{"name":"Replace your media buyer workflow","nodes":[{"id":"aaaaaaaa-0001-4bbb-8ccc-dddddddddddd","name":"Every Morning 07:00","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[-620,300],"parameters":{"rule":{"interval":[{"field":"days","daysInterval":1,"triggerAtHour":7}]}}},{"id":"aaaaaaaa-0002-4bbb-8ccc-dddddddddddd","name":"Pull Meta Ad Set Insights","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-360,140],"parameters":{"url":"https://graph.facebook.com/v21.0/act_ID/insights","sendQuery":true,"queryParameters":{"parameters":[{"name":"level","value":"adset"},{"name":"fields","value":"campaign_name,adset_id,adset_name,spend,impressions,clicks,ctr,frequency,purchase_roas,actions"},{"name":"date_preset","value":"last_7d"},{"name":"limit","value":"200"}]},"options":{}}},{"id":"aaaaaaaa-0003-4bbb-8ccc-dddddddddddd","name":"Pull Google Ads Campaigns","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-360,460],"parameters":{"method":"POST","url":"https://googleads.googleapis.com/v18/customers/CUSTOMER_ID/googleAds:searchStream","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ query: \"SELECT campaign.id, campaign.name, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.impressions, metrics.clicks, metrics.ctr FROM campaign WHERE segments.date DURING LAST_7_DAYS AND campaign.status = 'ENABLED'\" }) }}","options":{}}},{"id":"aaaaaaaa-0004-4bbb-8ccc-dddddddddddd","name":"Combine Platforms","type":"n8n-nodes-base.merge","typeVersion":3,"position":[-100,300],"parameters":{"mode":"append","options":{}}},{"id":"aaaaaaaa-0005-4bbb-8ccc-dddddddddddd","name":"Score & Decide","type":"n8n-nodes-base.code","typeVersion":2,"position":[160,300],"parameters":{"jsCode":"// ── Normalise Meta + Google rows into one decision table ──────────────────────\n// Meta rows arrive with: campaign_name, adset_id, adset_name, spend, impressions,\n// clicks, ctr, frequency, purchase_roas[], actions[]\n// Google rows arrive with: campaign.name, campaign.id, metrics.cost_micros,\n// metrics.conversions, metrics.conversions_value, metrics.impressions, metrics.ctr\n\nconst TARGET_ROAS      = 2.2;   // account breakeven 1.6, target with margin\nconst SCALE_ROAS       = 2.8;   // scale only when comfortably above target\nconst KILL_ROAS        = 1.2;   // below breakeven for real\nconst MIN_SPEND        = 75;    // don't judge an ad set on noise\nconst FREQ_FATIGUE     = 2.6;   // Meta 7d frequency where CPA usually rots\nconst CTR_FATIGUE      = 0.9;   // % — creative has stopped earning attention\nconst MAX_SCALE_PCT    = 0.20;  // never move budget more than 20% a day\nconst DAILY_BUDGET_CAP = 4000;  // account-level guardrail\nconst MONTHLY_TARGET   = 90000; // pacing target for the month\n\nconst raw = $input.all().map(i => i.json);\nconst rows = [];\n\nfor (const r of raw) {\n  let row;\n  if (r.adset_id || r.purchase_roas !== undefined) {\n    const roasArr = Array.isArray(r.purchase_roas) ? r.purchase_roas : [];\n    const roas = roasArr.length ? parseFloat(roasArr[0].value) : parseFloat(r.purchase_roas || 0);\n    const purch = (r.actions || []).find(a => a.action_type === 'purchase');\n    row = {\n      platform: 'meta',\n      entity_id: r.adset_id || r.campaign_id,\n      entity_name: r.adset_name || r.campaign_name,\n      campaign_name: r.campaign_name,\n      spend: parseFloat(r.spend || 0),\n      impressions: parseInt(r.impressions || 0, 10),\n      clicks: parseInt(r.clicks || 0, 10),\n      ctr: parseFloat(r.ctr || 0),\n      frequency: parseFloat(r.frequency || 0),\n      conversions: purch ? parseFloat(purch.value) : 0,\n      roas,\n      daily_budget: parseFloat(r.daily_budget || 0) / 100,\n    };\n  } else {\n    const c = r.campaign || {};\n    const m = r.metrics || {};\n    const spend = (parseInt(m.cost_micros || 0, 10)) / 1e6;\n    const value = parseFloat(m.conversions_value || 0);\n    row = {\n      platform: 'google',\n      entity_id: String(c.id || ''),\n      entity_name: c.name || '',\n      campaign_name: c.name || '',\n      spend,\n      impressions: parseInt(m.impressions || 0, 10),\n      clicks: parseInt(m.clicks || 0, 10),\n      ctr: parseFloat(m.ctr || 0) * 100,\n      frequency: 0,\n      conversions: parseFloat(m.conversions || 0),\n      roas: spend > 0 ? value / spend : 0,\n      daily_budget: parseInt(c.campaign_budget_amount_micros || 0, 10) / 1e6,\n    };\n  }\n  if (!row.entity_id) continue;\n  rows.push(row);\n}\n\n// ── dedupe: same entity can appear twice if the date windows overlap ─────────\nconst byId = new Map();\nfor (const r of rows) {\n  const key = r.platform + ':' + r.entity_id;\n  const prev = byId.get(key);\n  if (!prev || r.spend > prev.spend) byId.set(key, r);\n}\nconst unique = [...byId.values()];\n\nconst accountSpend = unique.reduce((s, r) => s + r.spend, 0);\nconst accountRev   = unique.reduce((s, r) => s + r.spend * r.roas, 0);\nconst accountRoas  = accountSpend > 0 ? accountRev / accountSpend : 0;\nconst dayOfMonth   = new Date().getDate();\nconst daysInMonth  = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();\nconst projected    = accountSpend * daysInMonth;              // naive month projection\nconst pacingIndex  = MONTHLY_TARGET > 0 ? projected / MONTHLY_TARGET : 1;\n\nconst out = unique.map(r => {\n  const cpa = r.conversions > 0 ? r.spend / r.conversions : null;\n  const cpm = r.impressions > 0 ? (r.spend / r.impressions) * 1000 : 0;\n\n  // health score 0-100: efficiency 55%, volume confidence 20%, freshness 25%\n  const eff   = Math.max(0, Math.min(1, r.roas / (TARGET_ROAS * 1.5)));\n  const conf  = Math.max(0, Math.min(1, r.spend / (MIN_SPEND * 4)));\n  const fresh = r.platform === 'meta'\n    ? Math.max(0, Math.min(1, 1 - (r.frequency - 1.4) / (FREQ_FATIGUE - 1.4)))\n    : Math.max(0, Math.min(1, r.ctr / 2.5));\n  const score = Math.round((eff * 55) + (conf * 20) + (fresh * 25));\n\n  const fatigued = r.platform === 'meta'\n    ? (r.frequency >= FREQ_FATIGUE && r.ctr < CTR_FATIGUE)\n    : (r.ctr < CTR_FATIGUE && r.impressions > 20000);\n\n  let action = 'hold', reason = '', delta = 0;\n\n  if (r.spend < MIN_SPEND) {\n    action = 'hold';\n    reason = 'only $' + r.spend.toFixed(0) + ' spent — below the $' + MIN_SPEND + ' judgement floor';\n  } else if (r.roas < KILL_ROAS) {\n    action = 'pause';\n    reason = 'ROAS ' + r.roas.toFixed(2) + ' is under the ' + KILL_ROAS + ' kill line on $' + r.spend.toFixed(0) + ' spend';\n  } else if (fatigued && r.roas < TARGET_ROAS) {\n    action = 'refresh';\n    reason = 'frequency ' + r.frequency.toFixed(2) + ' / CTR ' + r.ctr.toFixed(2) + '% — creative is burnt, ROAS slid to ' + r.roas.toFixed(2);\n  } else if (r.roas >= SCALE_ROAS && score >= 70) {\n    // scale step scales with conviction, capped at MAX_SCALE_PCT\n    const conviction = Math.min(1, (r.roas - SCALE_ROAS) / SCALE_ROAS + conf * 0.5);\n    let pct = Math.round(MAX_SCALE_PCT * 100 * Math.max(0.5, conviction));\n    if (pacingIndex > 1.15) pct = Math.round(pct / 2);        // already over-pacing\n    delta = pct;\n    action = 'scale';\n    reason = 'ROAS ' + r.roas.toFixed(2) + ' with health ' + score + '/100 — earned a +' + pct + '% budget step';\n  } else if (r.roas < TARGET_ROAS && r.roas >= KILL_ROAS) {\n    action = 'trim';\n    delta = -15;\n    reason = 'ROAS ' + r.roas.toFixed(2) + ' under target ' + TARGET_ROAS + ' — pull 15% of budget back';\n  } else {\n    action = 'hold';\n    reason = 'ROAS ' + r.roas.toFixed(2) + ' inside the target band — leave it alone';\n  }\n\n  const newBudget = delta !== 0 && r.daily_budget > 0\n    ? Math.min(DAILY_BUDGET_CAP, Math.round(r.daily_budget * (1 + delta / 100)))\n    : r.daily_budget;\n\n  return {\n    json: {\n      ...r,\n      cpa,\n      cpm: Number(cpm.toFixed(2)),\n      health_score: score,\n      fatigued,\n      action,\n      delta_pct: delta,\n      new_daily_budget: newBudget,\n      rule_reason: reason,\n      needs_action: action !== 'hold',\n      account_roas: Number(accountRoas.toFixed(2)),\n      account_spend: Number(accountSpend.toFixed(2)),\n      pacing_index: Number(pacingIndex.toFixed(2)),\n      projected_month_spend: Math.round(projected),\n      day_of_month: dayOfMonth,\n      decision_date: new Date().toISOString().slice(0, 10),\n    },\n  };\n});\n\n// highest spend first so the biggest money moves are executed first\nout.sort((a, b) => b.json.spend - a.json.spend);\nreturn out;"}},{"id":"aaaaaaaa-0006-4bbb-8ccc-dddddddddddd","name":"Anything To Do?","type":"n8n-nodes-base.if","typeVersion":2,"position":[420,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.needs_action }}","rightValue":"","operator":{"type":"boolean","operation":"true","singleValue":true}}]},"options":{}}},{"id":"aaaaaaaa-0007-4bbb-8ccc-dddddddddddd","name":"Write Decision Rationale","type":"@n8n/n8n-nodes-langchain.openAi","typeVersion":1.8,"position":[680,120],"parameters":{"modelId":{"__rl":true,"value":"gpt-4o-mini","mode":"list"},"messages":{"values":[{"role":"system","content":"You are a senior media buyer writing the one-line note you would leave in a client change log. State the decision, the number that drove it, and the risk you are accepting. No fluff, no hedging, max 40 words."},{"content":"=Platform: {{ $json.platform }}\nEntity: {{ $json.entity_name }}\nAction: {{ $json.action }} ({{ $json.delta_pct }}%)\n7d spend: ${{ $json.spend }}\nROAS: {{ $json.roas }} (target 2.2)\nCTR: {{ $json.ctr }}%\nFrequency: {{ $json.frequency }}\nHealth score: {{ $json.health_score }}/100\nBudget: ${{ $json.daily_budget }} -> ${{ $json.new_daily_budget }}\nRule fired: {{ $json.rule_reason }}\nAccount pacing index: {{ $json.pacing_index }}"}]},"options":{}}},{"id":"aaaaaaaa-0008-4bbb-8ccc-dddddddddddd","name":"Build Platform Payload","type":"n8n-nodes-base.code","typeVersion":2,"position":[940,120],"parameters":{"jsCode":"// ── Turn a decision + its LLM rationale into the exact API payload ───────────\n// The LLM node replaces the item json with { message: { content } }, so the\n// decision itself is read back from the branch that fed it, paired by index.\nconst decisions = $('Anything To Do?').all().map(i => i.json);\n\nreturn $input.all().map((item, idx) => {\n  const d = decisions[idx] || item.json;\n  const llm = item.json || {};\n  const rationale = (llm.message?.content || llm.content || llm.text || d.rule_reason || '').trim();\n\n  let endpoint = '', body = {};\n  if (d.platform === 'meta') {\n    endpoint = 'https://graph.facebook.com/v21.0/' + d.entity_id;\n    if (d.action === 'pause')        body = { status: 'PAUSED' };\n    else if (d.action === 'refresh') body = { status: 'PAUSED', name: d.entity_name + ' [CREATIVE REFRESH QUEUED]' };\n    else                             body = { daily_budget: Math.round(d.new_daily_budget * 100) };\n  } else {\n    endpoint = 'https://googleads.googleapis.com/v18/customers/CUSTOMER_ID/campaigns:mutate';\n    body = d.action === 'pause'\n      ? { operations: [{ update: { resourceName: 'customers/CUSTOMER_ID/campaigns/' + d.entity_id, status: 'PAUSED' }, updateMask: 'status' }] }\n      : { operations: [{ update: { resourceName: 'customers/CUSTOMER_ID/campaigns/' + d.entity_id, campaignBudget: { amountMicros: Math.round(d.new_daily_budget * 1e6) } }, updateMask: 'campaign_budget.amount_micros' }] };\n  }\n\n  return {\n    json: {\n      ...d,\n      rationale,\n      endpoint,\n      request_body: body,\n      is_budget_move: d.action === 'scale' || d.action === 'trim',\n      is_pause: d.action === 'pause',\n      executed_at: new Date().toISOString(),\n    },\n  };\n});"}},{"id":"aaaaaaaa-0009-4bbb-8ccc-dddddddddddd","name":"Budget Move Or Kill?","type":"n8n-nodes-base.if","typeVersion":2,"position":[1200,120],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.is_budget_move }}","rightValue":"","operator":{"type":"boolean","operation":"true","singleValue":true}}]},"options":{}}},{"id":"aaaaaaaa-0010-4bbb-8ccc-dddddddddddd","name":"Apply New Daily Budget","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1460,-20],"parameters":{"method":"POST","url":"={{ $json.endpoint }}","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.request_body) }}","options":{}}},{"id":"aaaaaaaa-0011-4bbb-8ccc-dddddddddddd","name":"Pause Or Refresh?","type":"n8n-nodes-base.if","typeVersion":2,"position":[1460,240],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.action }}","rightValue":"pause","operator":{"type":"string","operation":"equals"}}]},"options":{}}},{"id":"aaaaaaaa-0012-4bbb-8ccc-dddddddddddd","name":"Pause Losing Ad Set","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1720,120],"parameters":{"method":"POST","url":"={{ $json.endpoint }}","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify($json.request_body) }}","options":{}}},{"id":"aaaaaaaa-0013-4bbb-8ccc-dddddddddddd","name":"Request Creative Refresh","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[1720,360],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#creative-requests","mode":"name"},"text":"=🎨 Creative burnt: *{{ $json.entity_name }}* — freq {{ $json.frequency }}, CTR {{ $json.ctr }}%, ROAS {{ $json.roas }}. Need 3 new hooks by tomorrow.\n_{{ $json.rationale }}_","otherOptions":{}}},{"id":"aaaaaaaa-0014-4bbb-8ccc-dddddddddddd","name":"Build Watchlist Row","type":"n8n-nodes-base.code","typeVersion":2,"position":[680,500],"parameters":{"jsCode":"// ── Held ad sets still get logged so the report shows the whole account ──────\nreturn $input.all().map(item => {\n  const d = item.json;\n  const distance = Number((d.roas - 2.2).toFixed(2));   // gap to target ROAS\n  return {\n    json: {\n      ...d,\n      rationale: d.rule_reason,\n      executed: false,\n      execution_note: 'no change — watchlist only',\n      roas_gap_to_target: distance,\n      watch_priority: d.spend > 300 && distance < 0.3 ? 'high' : 'normal',\n      executed_at: new Date().toISOString(),\n    },\n  };\n});"}},{"id":"aaaaaaaa-0015-4bbb-8ccc-dddddddddddd","name":"Log Decision To Sheet","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1980,300],"parameters":{"operation":"append","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"Decision Log"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[]},"options":{}}},{"id":"aaaaaaaa-0016-4bbb-8ccc-dddddddddddd","name":"Hold Until End Of Day","type":"n8n-nodes-base.wait","typeVersion":1.1,"position":[2240,300],"parameters":{"amount":10,"unit":"hours"}},{"id":"aaaaaaaa-0017-4bbb-8ccc-dddddddddddd","name":"Build EOD Report","type":"n8n-nodes-base.code","typeVersion":2,"position":[2500,300],"parameters":{"jsCode":"// ── End-of-day report: what moved, what it cost, where the month is heading ──\nconst rows = $input.all().map(i => i.json);\nconst by = a => rows.filter(r => r.action === a);\n\nconst spend   = rows.reduce((s, r) => s + (r.spend || 0), 0);\nconst revenue = rows.reduce((s, r) => s + (r.spend || 0) * (r.roas || 0), 0);\nconst roas    = spend > 0 ? revenue / spend : 0;\nconst conv    = rows.reduce((s, r) => s + (r.conversions || 0), 0);\n\nconst scaled  = by('scale');\nconst paused  = by('pause');\nconst trimmed = by('trim');\nconst refresh = by('refresh');\n\nconst budgetAdded   = scaled.reduce((s, r) => s + (r.new_daily_budget - r.daily_budget), 0);\nconst budgetFreed   = trimmed.reduce((s, r) => s + (r.daily_budget - r.new_daily_budget), 0)\n                    + paused.reduce((s, r) => s + (r.daily_budget || 0), 0);\nconst netBudgetMove = budgetAdded - budgetFreed;\n\nconst pacing = rows[0]?.pacing_index || 1;\nconst pacingLine = pacing > 1.1 ? 'OVER pace — projected $' + (rows[0]?.projected_month_spend || 0).toLocaleString() + ' vs $90,000 target'\n  : pacing < 0.9 ? 'UNDER pace — room to push winners harder'\n  : 'on pace';\n\nconst line = r => '<li><b>' + r.entity_name + '</b> (' + r.platform + ') — ROAS ' +\n  (r.roas || 0).toFixed(2) + ', $' + (r.spend || 0).toFixed(0) + ' spend, health ' +\n  (r.health_score ?? '-') + '/100<br><i>' + (r.rationale || r.rule_reason || '') + '</i></li>';\n\nconst section = (title, arr) => arr.length\n  ? '<h3>' + title + ' (' + arr.length + ')</h3><ul>' + arr.map(line).join('') + '</ul>'\n  : '';\n\nconst html = [\n  '<h2>Media buyer report — ' + new Date().toISOString().slice(0, 10) + '</h2>',\n  '<p>Spend <b>$' + spend.toFixed(0) + '</b> · Revenue <b>$' + revenue.toFixed(0) + '</b> · ' +\n    'Blended ROAS <b>' + roas.toFixed(2) + '</b> · Conversions <b>' + conv.toFixed(0) + '</b></p>',\n  '<p>Pacing: ' + pacingLine + '</p>',\n  '<p>Net daily budget moved: <b>' + (netBudgetMove >= 0 ? '+' : '') + '$' + netBudgetMove.toFixed(0) + '</b> ' +\n    '(+$' + budgetAdded.toFixed(0) + ' into winners, -$' + budgetFreed.toFixed(0) + ' out of losers)</p>',\n  section('Scaled', scaled),\n  section('Paused', paused),\n  section('Trimmed', trimmed),\n  section('Creative refresh queued', refresh),\n  section('Watchlist (no change)', rows.filter(r => r.action === 'hold').slice(0, 8)),\n].join('');\n\nconst summary = 'EOD: ' + scaled.length + ' scaled, ' + paused.length + ' paused, ' +\n  trimmed.length + ' trimmed, ' + refresh.length + ' queued for new creative. Blended ROAS ' +\n  roas.toFixed(2) + ' on $' + spend.toFixed(0) + '. ' + pacingLine + '.';\n\nreturn [{ json: { html, summary, spend, revenue, roas: Number(roas.toFixed(2)), decisions: rows.length } }];"}},{"id":"aaaaaaaa-0018-4bbb-8ccc-dddddddddddd","name":"Email Daily Report","type":"n8n-nodes-base.gmail","typeVersion":2.1,"position":[2760,160],"parameters":{"sendTo":"growth@brand.com","subject":"=Ads report {{ $now.format('yyyy-LL-dd') }} — ROAS {{ $json.roas }}","message":"={{ $json.html }}","options":{}}},{"id":"aaaaaaaa-0019-4bbb-8ccc-dddddddddddd","name":"Post EOD Summary","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[2760,440],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#ads-alerts","mode":"name"},"text":"={{ $json.summary }}","otherOptions":{}}},{"id":"aaaaaaaa-0020-4bbb-8ccc-dddddddddddd","name":"Note - Morning Pull","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[-680,-40],"parameters":{"content":"## 1. MORNING DATA PULL\nEvery day at 07:00 pull the last 7 days from Meta (ad set level) and Google Ads (campaign level), append both into one table.","height":300,"width":460,"color":4}},{"id":"aaaaaaaa-0021-4bbb-8ccc-dddddddddddd","name":"Note - Decision Engine","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[140,-100],"parameters":{"content":"## 2. RULES + RATIONALE\nOne Code node holds the whole strategy: kill under 1.2 ROAS, refresh when freq >2.6 and CTR <0.9%, scale over 2.8 ROAS with a health score >=70 (max +20%/day, halved when over-pacing), trim everything stuck under target. The LLM only writes the human explanation — it never chooses the action.","height":360,"width":520,"color":5}},{"id":"aaaaaaaa-0022-4bbb-8ccc-dddddddddddd","name":"Note - Execute And Report","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1440,-220],"parameters":{"content":"## 3. EXECUTE + REPORT\nBudget moves and pauses go straight back to the platform APIs, creative burnout becomes a Slack brief. Every row (including holds) lands in the sheet, then a 10h wait rolls up the day into an email + Slack summary.","height":320,"width":480,"color":3}}],"connections":{"Every Morning 07:00":{"main":[[{"node":"Pull Meta Ad Set Insights","type":"main","index":0},{"node":"Pull Google Ads Campaigns","type":"main","index":0}]]},"Pull Meta Ad Set Insights":{"main":[[{"node":"Combine Platforms","type":"main","index":0}]]},"Pull Google Ads Campaigns":{"main":[[{"node":"Combine Platforms","type":"main","index":1}]]},"Combine Platforms":{"main":[[{"node":"Score & Decide","type":"main","index":0}]]},"Score & Decide":{"main":[[{"node":"Anything To Do?","type":"main","index":0}]]},"Anything To Do?":{"main":[[{"node":"Write Decision Rationale","type":"main","index":0}],[{"node":"Build Watchlist Row","type":"main","index":0}]]},"Write Decision Rationale":{"main":[[{"node":"Build Platform Payload","type":"main","index":0}]]},"Build Platform Payload":{"main":[[{"node":"Budget Move Or Kill?","type":"main","index":0}]]},"Budget Move Or Kill?":{"main":[[{"node":"Apply New Daily Budget","type":"main","index":0}],[{"node":"Pause Or Refresh?","type":"main","index":0}]]},"Pause Or Refresh?":{"main":[[{"node":"Pause Losing Ad Set","type":"main","index":0}],[{"node":"Request Creative Refresh","type":"main","index":0}]]},"Apply New Daily Budget":{"main":[[{"node":"Log Decision To Sheet","type":"main","index":0}]]},"Pause Losing Ad Set":{"main":[[{"node":"Log Decision To Sheet","type":"main","index":0}]]},"Request Creative Refresh":{"main":[[{"node":"Log Decision To Sheet","type":"main","index":0}]]},"Build Watchlist Row":{"main":[[{"node":"Log Decision To Sheet","type":"main","index":0}]]},"Log Decision To Sheet":{"main":[[{"node":"Hold Until End Of Day","type":"main","index":0}]]},"Hold Until End Of Day":{"main":[[{"node":"Build EOD Report","type":"main","index":0}]]},"Build EOD Report":{"main":[[{"node":"Email Daily Report","type":"main","index":0},{"node":"Post EOD Summary","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"pinData":{}}