{
  "name": "DataSmithLab | Google Ads to Google Sheets",
  "nodes": [
    {
      "id": "59ac7529-05c1-4274-a34e-72714d29e14e",
      "name": "Manual start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ],
      "parameters": {}
    },
    {
      "id": "d9f318b1-54b9-4900-9c28-1d9ff22031f6",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        300
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ab75aeb6-58d4-49b0-919e-2b2959a47d0c",
              "name": "spreadsheetId",
              "type": "string",
              "value": "REPLACE_WITH_SPREADSHEET_ID"
            },
            {
              "id": "1624c286-8628-458b-8c9e-1d23b2e80d20",
              "name": "baselineOnEmptyScope",
              "type": "boolean",
              "value": true
            },
            {
              "id": "ec5cd70f-8eb4-4138-8fef-664e2a2d7a09",
              "name": "maxTotalChargeUsd",
              "type": "number",
              "value": 0.1
            },
            {
              "id": "8c32cec8-5612-4a49-a794-c90239e55724",
              "name": "actorInput",
              "type": "object",
              "value": "{\"domains\": [\"nike.com\"], \"region\": \"US\", \"resultType\": \"ads\", \"includeAdContent\": true, \"includeDetails\": false, \"onlyNewAds\": false, \"maxAdsPerTarget\": 20, \"maxItems\": 20}"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "b9cee195-210d-4f25-b03e-dd63bd133672",
      "name": "Validate configuration",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const c=$input.first().json;\nif (!c.spreadsheetId || c.spreadsheetId.startsWith('REPLACE_'))\n  throw new Error('Set spreadsheetId in Configuration.');\nconst a=c.actorInput;\nif (a.onlyNewAds !== false || a.resultType !== 'ads' || !Number.isInteger(a.maxItems) || a.maxItems<1 || a.maxItems>500)\n  throw new Error('Use ads snapshots, onlyNewAds=false, maxItems from 1 to 500.');\nif (!Array.isArray(a.domains) || a.domains.length!==1 || !/^[a-z0-9.-]+\\.[a-z]{2,}$/.test(a.domains[0]) || (a.queries?.length || a.advertiserIds?.length || a.startUrls?.length))\n  throw new Error('This starter supports one lowercase domain. Use a separate workflow/sheet for each competitor.');\nif (!Number.isFinite(c.maxTotalChargeUsd) || c.maxTotalChargeUsd<=0 || c.maxTotalChargeUsd>1)\n  throw new Error('Set a maximum run charge greater than zero and no more than $1.');\nreturn [{json:c}];"
      }
    },
    {
      "id": "1f9e86b7-3f5a-4a6f-b05b-5605cfa957d5",
      "name": "Read saved ads",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        880,
        300
      ],
      "parameters": {
        "resource": "sheet",
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configuration').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Ads",
          "mode": "name"
        },
        "options": {}
      },
      "alwaysOutputData": true
    },
    {
      "id": "1e3de104-6707-4330-aca8-aaa11b22237e",
      "name": "Start Actor",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        300
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/acts/datasmithlab~google-ads-transparency-scraper/runs?timeout=600&memory=256&maxTotalChargeUsd='+$('Configuration').first().json.maxTotalChargeUsd }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "method": "POST",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($('Configuration').first().json.actorInput) }}"
      },
      "executeOnce": true
    },
    {
      "id": "02649cd5-cc70-41b5-939d-6aca3e8d1302",
      "name": "Wait before poll",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1320,
        300
      ],
      "parameters": {
        "resume": "timeInterval",
        "amount": 5,
        "unit": "seconds"
      },
      "webhookId": "5940d0e1-28ad-4154-b566-15061d79c54b"
    },
    {
      "id": "2b67175e-8a6f-4043-b864-719d6e4ee158",
      "name": "Get run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1540,
        300
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/actor-runs/'+$('Start Actor').first().json.data.id+'?waitForFinish=60' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      }
    },
    {
      "id": "b1fb650e-acc1-4d54-bfaa-75199f805d84",
      "name": "Check run status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const r=$input.first().json.data;\nif (!r?.id) throw new Error('Missing Apify run response.');\nif (['FAILED','TIMED-OUT','ABORTED'].includes(r.status)) throw new Error(`Apify run ${r.id}: ${r.status}. Check its log.`);\nif (Date.now()-Date.parse($('Start Actor').first().json.data.startedAt)>15*60*1000)\n  throw new Error('Polling deadline reached. Inspect the existing Apify run before starting another.');\nreturn [{json:{...r,finished:r.status==='SUCCEEDED'}}];"
      }
    },
    {
      "id": "d4e2721c-b3b5-45f4-b407-ab04aabe59a3",
      "name": "Run finished",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1980,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "0fb8014b-0d70-4d9b-a20a-2b31e1fa4d8e",
              "leftValue": "={{ $json.finished }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "6ddfe160-188c-4d0f-a17f-74dcdbc2d5ba",
      "name": "Get HEALTH",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        300
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/key-value-stores/'+$('Check run status').first().json.defaultKeyValueStoreId+'/records/HEALTH' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      }
    },
    {
      "id": "d7d3e76d-abf5-4de3-b106-a8990d6cbf21",
      "name": "Get TARGETS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        300
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/key-value-stores/'+$('Check run status').first().json.defaultKeyValueStoreId+'/records/TARGETS' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json",
              "fullResponse": true
            }
          }
        }
      }
    },
    {
      "id": "3fd93e06-0c9e-4d3f-ae8b-453578e5d44f",
      "name": "Get ads",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        300
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/datasets/'+$('Check run status').first().json.defaultDatasetId+'/items?clean=true&limit='+($('Configuration').first().json.actorInput.maxItems+1) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json",
              "fullResponse": true
            }
          }
        }
      }
    },
    {
      "id": "26faeb3d-b762-424d-9f4a-1744355d8e50",
      "name": "Prepare delivery",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const c=$('Configuration').first().json, a=c.actorInput;\nconst run=$('Check run status').first().json;\nconst health=$('Get HEALTH').first().json;\nconst targets=$('Get TARGETS').first().json.body;\nconst ads=$input.first().json.body;\nif (!Array.isArray(ads) || !Array.isArray(targets) || !targets.length)\n  throw new Error('Missing dataset or TARGETS; no delivery state changed.');\nif (health.chargeLimitReached || health.malformedAdsSkipped>0 || health.adsWithTextOrDetailErrors>0 || targets.some(t=>!['ok','no_ads'].includes(t.status)))\n  throw new Error('Incomplete or unreliable source response. Inspect HEALTH and TARGETS before retrying.');\nif (health.rows!==ads.length || ads.length>a.maxItems || run.stats?.restartCount>0)\n  throw new Error('Dataset count mismatch or restarted run; inspect source before delivery.');\nconst scope=JSON.stringify({domain:a.domains[0],region:a.region||'US',platforms:[...(a.platforms||[])].sort(),\n  format:a.adFormat||'',from:a.startDate||'',until:a.endDate||''});\nconst existing=$('Read saved ads').all().map(i=>i.json).filter(r=>r.scope===scope && r.key);\nconst old=new Map();\nfor (const r of existing) {\n  if (old.has(r.key)) throw new Error('Duplicate saved key: fix sheet before retrying.');\n  if (!['baseline','logged'].includes(r.deliveryStatus)) throw new Error('Invalid deliveryStatus in saved sheet.');\n  old.set(r.key,r);\n}\nconst baseline=existing.length===0 && c.baselineOnEmptyScope;\nconst now=new Date().toISOString(), rows=new Map(old);\nfor (const ad of ads) {\n  if (!/^CR\\d+$/.test(ad.adId||'') || !/^AR\\d+$/.test(ad.advertiserId||''))\n    throw new Error('Missing or invalid ad identifier; no delivery state changed.');\n  const key=scope+'|'+ad.advertiserId+'|'+ad.adId;\n  const previous=old.get(key);\n  const plain=v=>String(v??'');\n  rows.set(key,{key,scope,adId:ad.adId,advertiserId:ad.advertiserId,\n    advertiserName:plain(ad.advertiserName),domain:plain(ad.domain),region:plain(ad.region),format:plain(ad.format),\n    headline:plain(ad.headline||ad.productTitle),description:plain(ad.description),\n    adUrl:`https://adstransparency.google.com/advertiser/${ad.advertiserId}/creative/${ad.adId}`,\n    firstShown:plain(ad.firstShown),lastShown:plain(ad.lastShown),firstSeenAt:previous?.firstSeenAt||now,\n    lastSeenAt:now,runId:run.id,deliveryStatus:previous?.deliveryStatus||(baseline?'baseline':'logged')});\n}\n// Keep pending rows even if a later snapshot does not contain them.\nconst newIds=[...rows.values()].filter(r=>!old.has(r.key)).map(r=>r.adId);\nconst pending=[...rows.values()].filter(r=>r.deliveryStatus==='pending');\nconst escape=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\nconst preview=pending.slice(0,5).map(r=>`\u2022 ${escape((r.headline||r.advertiserName||r.adId).slice(0,100))} \u2014 ${r.adUrl}`).join('\\n');\nconst slackText=`${pending.length} first-seen Google ad(s) for ${escape(a.domains[0])}.\\nPreviously unseen in this saved scope; publication time is not verified.\\n${preview}\\nAll records: https://docs.google.com/spreadsheets/d/${encodeURIComponent(c.spreadsheetId)}/edit\\nSnapshot limit: ${a.maxItems}. Source coverage is not guaranteed. Run: ${run.id}`;\nreturn [{json:{rows:[...rows.values()],pending,baseline,newIds,runId:run.id,\n  observedRows:ads.length,coverage:ads.length>=Math.min(a.maxItems,a.maxAdsPerTarget)?'at-row-limit':'below-row-limit-not-proof-of-completeness'}}];"
      }
    },
    {
      "id": "4f7f82b9-e38b-4b74-8965-972180b52292",
      "name": "Have rows",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "af217dce-3d4c-473b-977b-fecf6fd225ac",
              "leftValue": "={{ $json.rows.length > 0 }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "e89564a3-e402-4a1e-926f-7fa4188ee8ab",
      "name": "Expand rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3300,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "return $('Prepare delivery').first().json.rows.map(json=>({json}));"
      }
    },
    {
      "id": "b164f068-ae76-4664-bbf3-036ca06f2326",
      "name": "Save ads",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3520,
        300
      ],
      "parameters": {
        "resource": "sheet",
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configuration').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Ads",
          "mode": "name"
        },
        "options": {
          "cellFormat": "RAW",
          "handlingExtraData": "ignoreIt"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "key"
          ],
          "schema": [
            {
              "id": "key",
              "displayName": "key",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "scope",
              "displayName": "scope",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "adId",
              "displayName": "adId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "advertiserId",
              "displayName": "advertiserId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "advertiserName",
              "displayName": "advertiserName",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "domain",
              "displayName": "domain",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "region",
              "displayName": "region",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "format",
              "displayName": "format",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "headline",
              "displayName": "headline",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "adUrl",
              "displayName": "adUrl",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "firstShown",
              "displayName": "firstShown",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "lastShown",
              "displayName": "lastShown",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "firstSeenAt",
              "displayName": "firstSeenAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "lastSeenAt",
              "displayName": "lastSeenAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "runId",
              "displayName": "runId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "deliveryStatus",
              "displayName": "deliveryStatus",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      }
    },
    {
      "id": "c7c2e917-74de-40e8-920c-c761ada305a4",
      "name": "Run summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4840,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const d=$('Prepare delivery').first().json;return [{json:{runId:d.runId,observedRows:d.observedRows,savedRows:d.rows.length,baseline:d.baseline,newIds:d.newIds,coverage:d.coverage,deliveryMode:'sheets-only',note:'No notification sent. First-seen IDs do not establish publication time.'}}];"
      }
    },
    {
      "id": "bb071fef-2e67-4ccb-b451-2bf373f090fb",
      "name": "Setup and delivery notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        5060,
        300
      ],
      "parameters": {
        "content": "# Track competitor Google ads in Google Sheets with Apify\n\n## Who is this for?\n\nMarketing analysts who want a repeatable, bounded log of a competitor's Google ads without maintaining scraper infrastructure.\n\n## What it does\n\nThe workflow reads saved keys, launches one Apify Actor, waits for completion, checks HEALTH/TARGETS and upserts records into Google Sheets. An empty scope starts a baseline. Later unseen IDs are logged; first-seen time does not prove publication time. Older rows are retained.\n\n## Setup\n\nCreate a private Ads tab using the supplied CSV headers. Import the inactive workflow. In Configuration, set your spreadsheet ID and Actor input. Connect Header Auth on five Apify HTTP nodes and Google Sheets OAuth2 on both Sheets nodes. Run manually twice, inspect reports and check stable keys before activating the daily 09:00 UTC schedule. Keep Start Actor's Execute Once enabled and run sequentially.\n\n## Requirements and customization\n\nUse your own Apify account/token and Google spreadsheet edit access. Defaults: one domain, US, 20 ads, details off, Actor tracking off and a $0.10 Actor charge cap. Hosting and other service costs are separate. Change the domain/region for your use case; each filter scope establishes its own baseline.\n\nTested on self-hosted n8n 2.26.4. Other environments need validation. This is not a complete competitor inventory and sends no notifications.\n\n[Setup guide and headers](https://github.com/Datasmith-lab/datasmithlab-google-ads-n8n#setup)\n",
        "height": 1000,
        "width": 620,
        "color": 5
      }
    },
    {
      "id": "4dfffd99-1a6d-4563-9fdc-14fe55fa03ab",
      "name": "Daily 09 UTC",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        220,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "9b6b07ee-8de1-42e9-a99e-8b7d68b6817c",
      "name": "1 \u00b7 Open Actor & prepare Ads tab",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -600,
        -500
      ],
      "parameters": {
        "content": "## 1. Prepare your accounts\n[Open Google Ads Transparency Scraper on Apify](https://apify.com/datasmithlab/google-ads-transparency-scraper)\nCheck Actor access and pricing.\nCreate a private Sheet with a tab named **Ads**.\nPaste ads-sheet-headers.csv into row 1.\nKeep this imported workflow inactive.",
        "height": 400,
        "width": 500
      }
    },
    {
      "id": "14807c88-2ed5-4d2c-9df4-08f52922ff89",
      "name": "2 \u00b7 Configure your snapshot",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        -500
      ],
      "parameters": {
        "content": "## 2. Open Configuration (Edit Fields)\nReplace **REPLACE_WITH_SPREADSHEET_ID** in the spreadsheetId field with the text between /d/ and /edit in your Sheet URL.\nSet one lowercase domain and region.\nStart with maxItems=20, maxAdsPerTarget=20.\nKeep onlyNewAds=false: Sheets stores observed IDs.\nDefault Actor charge cap: $0.10; check current prices.",
        "height": 400,
        "width": 500
      }
    },
    {
      "id": "a7a0646d-c699-406a-b2c2-de8213dc60d9",
      "name": "3 \u00b7 Connect credentials",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        500,
        -500
      ],
      "parameters": {
        "content": "## 3. Connect your own credentials\nApify Header Auth: name **Authorization**, value **Bearer YOUR_APIFY_TOKEN**.\nSelect it on Start Actor, Get run, Get HEALTH, Get TARGETS, Get ads.\nGoogle Sheets OAuth2: select on Read saved ads and Save ads.\nNever paste real tokens into code or public exports.",
        "height": 400,
        "width": 500
      }
    },
    {
      "id": "d9789ce0-d129-4953-a5c3-d439428e9e33",
      "name": "4 \u00b7 Verify before scheduling",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1050,
        -500
      ],
      "parameters": {
        "content": "## 4. Run Manual start twice\nFirst: SUCCEEDED Actor, HEALTH/TARGETS checked, baseline rows in Ads.\nRun summary: observedRows <=20; savedRows matches this scope's keys.\nSecond: repeated IDs update, not duplicate; firstSeenAt stays unchanged.\nFresh unseen IDs may legitimately increase savedRows.\nKeep Start Actor Execute Once enabled.\nOnly then activate daily 09:00 UTC. One execution at a time.",
        "height": 400,
        "width": 500
      }
    }
  ],
  "connections": {
    "Manual start": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "Validate configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate configuration": {
      "main": [
        [
          {
            "node": "Read saved ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read saved ads": {
      "main": [
        [
          {
            "node": "Start Actor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Actor": {
      "main": [
        [
          {
            "node": "Wait before poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait before poll": {
      "main": [
        [
          {
            "node": "Get run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get run": {
      "main": [
        [
          {
            "node": "Check run status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check run status": {
      "main": [
        [
          {
            "node": "Run finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run finished": {
      "main": [
        [
          {
            "node": "Get HEALTH",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait before poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get HEALTH": {
      "main": [
        [
          {
            "node": "Get TARGETS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get TARGETS": {
      "main": [
        [
          {
            "node": "Get ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get ads": {
      "main": [
        [
          {
            "node": "Prepare delivery",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare delivery": {
      "main": [
        [
          {
            "node": "Have rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Have rows": {
      "main": [
        [
          {
            "node": "Expand rows",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Run summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand rows": {
      "main": [
        [
          {
            "node": "Save ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save ads": {
      "main": [
        [
          {
            "node": "Run summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily 09 UTC": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "UTC",
    "executionTimeout": 1200
  },
  "pinData": {},
  "id": "DataSmithLabAdsSheetsStarter"
}