{
  "name": "Agent 1 (Tavily): Prospect Hunter — finds new prospects daily",
  "description": null,
  "active": false,
  "nodes": [
    {
      "parameters": {},
      "id": "t_manual",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -608,
        0
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            },
            {
              "field": "cronExpression",
              "expression": "0 17 * * *"
            }
          ]
        }
      },
      "id": "t_sched",
      "name": "Twice Daily — 6 AM & 5 PM IST",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -608,
        192
      ]
    },
    {
      "parameters": {
        "jsCode": "// MULTI-NICHE INTERNATIONAL hunter with coverage-aware skipping.\n// Coverage engine unchanged: skips exhausted (<5 uniques) combos, retries productive after 21 days.\nconst CITIES = [\n  ...[\"New York\",\"Los Angeles\",\"San Francisco\",\"San Diego\",\"Miami\",\"Naples FL\",\"Boca Raton\",\"Palm Beach\",\"Chicago\",\"Boston\",\"Seattle\",\"Dallas\",\"Houston\",\"Austin\",\"Atlanta\",\"Phoenix\",\"Scottsdale\",\"Denver\",\"Washington DC\",\"Philadelphia\",\"Minneapolis\",\"Charlotte\",\"Nashville\",\"Las Vegas\",\"Portland\",\"Sarasota FL\",\"San Jose\",\"Tampa\",\"Orlando\"].map(c=>({city:c,country:\"USA\"})),\n  ...[\"Dubai\",\"Abu Dhabi\",\"Sharjah\"].map(c=>({city:c,country:\"UAE\"})),\n  ...[\"London\",\"Manchester\",\"Birmingham\",\"Leeds\",\"Edinburgh\"].map(c=>({city:c,country:\"UK\"})),\n  ...[\"Singapore\"].map(c=>({city:c,country:\"Singapore\"})),\n  ...[\"Toronto\",\"Vancouver\",\"Calgary\",\"Mississauga\"].map(c=>({city:c,country:\"Canada\"})),\n  ...[\"Sydney\",\"Melbourne\",\"Brisbane\",\"Perth\"].map(c=>({city:c,country:\"Australia\"})),\n];\nconst INDIA_CITIES = [\"Delhi\",\"Gurgaon\",\"Noida\",\"Mumbai\",\"Navi Mumbai\",\"Pune\",\"Bangalore\",\"Hyderabad\",\"Chennai\"].map(c=>({city:c,country:\"India\"}));\nconst NICHE_MAP = {\n  \"luxury assisted living facility\":\"assisted_living\",\n  \"premium senior living community\":\"assisted_living\",\n  \"premium memory care / dementia care facility\":\"dementia_care\",\n  \"premium independent living for affluent seniors\":\"independent_living\",\n  \"premium home health / nursing agency for seniors\":\"elder_homecare\",\n  \"premium senior home-care agency / franchise\":\"elder_homecare\",\n  \"concierge geriatric / house-call doctor for seniors\":\"doctor_on_call\",\n  \"premium doctor-on-call / mobile physician service\":\"doctor_on_call\",\n  \"timeshare exit company\":\"timeshare_exit\",\n  \"timeshare cancellation service\":\"timeshare_exit\",\n  \"private luxury addiction treatment center\":\"rehab_treatment\",\n  \"executive alcohol and drug rehab\":\"rehab_treatment\",\n  \"premium detox and dual-diagnosis facility\":\"rehab_treatment\",\n  \"tax debt relief firm\":\"tax_debt_relief\",\n  \"tax resolution services company\":\"tax_debt_relief\",\n  \"cosmetic dentistry clinic for international patients\":\"medical_tourism\",\n  \"hair transplant clinic\":\"medical_tourism\",\n  \"bariatric surgery center\":\"medical_tourism\",\n  \"IVF and fertility clinic\":\"medical_tourism\",\n  \"equipment financing company\":\"equipment_financing\",\n  \"commercial equipment leasing broker\":\"equipment_financing\",\n  \"medical equipment financing firm\":\"equipment_financing\",\n  \"citizenship by investment consultancy\":\"golden_visa\",\n  \"golden visa and residency services firm\":\"golden_visa\"\n};\nconst ALL = [\"USA\",\"UAE\",\"UK\",\"Singapore\",\"Canada\",\"Australia\",\"India\"];\nconst NICHE_GEO = {\n  assisted_living:ALL, dementia_care:ALL, independent_living:ALL, elder_homecare:ALL, doctor_on_call:ALL,\n  timeshare_exit:[\"USA\",\"UK\",\"Canada\",\"Australia\"],\n  rehab_treatment:[\"USA\",\"UK\",\"Canada\",\"Australia\",\"UAE\"],\n  tax_debt_relief:[\"USA\",\"UK\",\"Canada\",\"Australia\"],\n  medical_tourism:[\"India\",\"UAE\",\"Singapore\",\"UK\"],\n  equipment_financing:ALL,\n  golden_visa:[\"UAE\",\"Singapore\",\"UK\",\"India\",\"Canada\",\"Australia\"]\n};\nconst NICHES = Object.keys(NICHE_MAP);\nfunction build(cities){ const o=[]; for(const c of cities) for(const niche of NICHES){\n  const slug=NICHE_MAP[niche];\n  if(!(NICHE_GEO[slug]||ALL).includes(c.country)) continue;\n  o.push({city:c.city,country:c.country,niche,niche_slug:slug}); } return o; }\nconst intlAll = build(CITIES);\nconst indiaAll = build(INDIA_CITIES);\n\nlet coverage=[];\ntry { coverage = $('Fetch Coverage').all().map(i=>i.json)\n  .filter(c => c && c.city && (c.exhausted!==undefined || c.unique_found!==undefined)); } catch(e){}\nconst key=(c,n)=>`${(c||'').toLowerCase().trim()}|${(n||'').toLowerCase().trim()}`;\nconst status={};\nfor(const c of coverage){\n  const k=key(c.city,c.niche_slug); const t=c.scraped_at||'';\n  if(!status[k] || t > (status[k].t||'')) status[k]={exhausted:!!c.exhausted, t};\n}\nconst now=Date.now();\nfunction eligible(x){\n  const s=status[key(x.city,x.niche_slug)];\n  if(!s) return true;\n  if(s.exhausted) return false;\n  const age=(now - new Date(s.t).getTime())/(24*3600*1000);\n  return isFinite(age) ? age>21 : true;\n}\nfunction spread(pool){\n  const elig=pool.filter(eligible);\n  const byCity={};\n  for(const c of elig){ (byCity[c.city]=byCity[c.city]||[]).push(c); }\n  const cities=Object.keys(byCity);\n  const out=[]; let guard=0;\n  while(cities.some(ct=>byCity[ct].length) && guard++<20000){\n    for(const ct of cities){ const a=byCity[ct]; if(a.length) out.push(a.shift()); }\n  }\n  return out;\n}\nconst BATCH=14, INTL_N=11;\nconst intl=spread(intlAll), india=spread(indiaAll);\nconst picked=[];\nfor(let i=0;i<INTL_N && i<intl.length;i++) picked.push(intl[i]);\nfor(let i=0;i<(BATCH-picked.length) && i<india.length;i++) picked.push(india[i]);\nfor(let i=INTL_N;i<intl.length && picked.length<BATCH;i++) picked.push(intl[i]);\nreturn picked.map(json=>({json}));\n"
      },
      "id": "n_combos",
      "name": "Build City+Niche Combos",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        64,
        96
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst existing = $('Fetch Existing Prospects').all();\nconst knownByCity = {};\nfor (const r of existing) {\n  const f = r.json.fields || r.json;\n  const city = (f.city || '').toLowerCase().trim();\n  if (!knownByCity[city]) knownByCity[city] = [];\n  if (f.company) knownByCity[city].push(f.company);\n}\nconst BUDGET = { India:\"a Rs 1,00,000+/month\", UAE:\"an AED 18,000+/month\", USA:\"a $5,000+/month\", UK:\"a GBP 4,000+/month\", Singapore:\"an SGD 6,500+/month\", Canada:\"a CAD 6,500+/month\", Australia:\"an AUD 7,000+/month\" };\nconst PROFILES = {\n  timeshare_exit:{\n    target:\"- Established timeshare exit / cancellation companies with real case results, attorney partnerships, or escrow-based models.\\n- Professional brand, active website, visible team, real reviews.\\n- Monetizing high-value exit cases (typical fees $3,000-$10,000 per client).\",\n    exclude:\"- Obvious scams, fly-by-night operations, review-bombed companies with no physical presence.\\n- Law firms that only incidentally touch timeshare law.\\n- Timeshare RESALE listing marketplaces (different business).\"},\n  rehab_treatment:{\n    target:\"- Private-pay, luxury, or executive addiction treatment centers, detox facilities, and dual-diagnosis programs.\\n- Accredited (JCAHO/CARF or local equivalent), professionally branded, insurance- or self-pay funded.\\n- Residential or intensive outpatient programs with premium facilities.\",\n    exclude:\"- State-funded, free, or charity treatment programs.\\n- 12-step meeting groups, sober-living-only houses, unaccredited operations.\\n- Directories and lead brokers (we want the actual treatment centers).\"},\n  tax_debt_relief:{\n    target:\"- Licensed tax resolution firms (enrolled agents, CPAs, tax attorneys) handling IRS/state tax debt cases.\\n- Real credentials and case results, charging $2,000-$10,000+ per case.\",\n    exclude:\"- Unlicensed 'pennies on the dollar' boiler rooms or FTC-flagged operators.\\n- Generic accounting firms without a tax-debt practice.\\n- Lead-gen websites that are not actual firms.\"},\n  medical_tourism:{\n    target:\"- Private clinics actively serving international or out-of-town patients: cosmetic dentistry/veneers, hair transplant, bariatric surgery, IVF/fertility.\\n- Modern facilities, English-language or international-patient pages, published doctor profiles, before/after galleries.\\n- High-ticket procedures with premium pricing.\",\n    exclude:\"- Government hospitals and charity clinics.\\n- Aggregator/booking platforms and medical tourism directories (we want the clinics themselves).\\n- Unaccredited or anonymous clinics with no doctor names.\"},\n  equipment_financing:{\n    target:\"- Direct equipment financing lenders and established commercial leasing brokers (construction, medical, restaurant, transport equipment).\\n- Real underwriting or funded-deal track record, business-facing brand, deal sizes $50k+.\",\n    exclude:\"- Consumer loan brokers and payday-style lenders.\\n- Pure marketplace/aggregator sites.\\n- Giant national banks (they will not buy agency SEO retainers).\"},\n  golden_visa:{\n    target:\"- Citizenship-by-investment and golden visa / residency advisory firms with real government program partnerships (UAE, Portugal, Caribbean, EU programs).\\n- HNW-facing brand, service tickets $10,000-$50,000+, professional multilingual team.\",\n    exclude:\"- Generic immigration consultants doing only work/student visas.\\n- Unlicensed one-person visa agents.\\n- Content/affiliate sites that are not actual advisory firms.\"},\n  _elder:{\n    target:\"- Privately-run, professionally-branded, PAID facilities/brands serving affluent or upper-middle-class older people and their families.\\n- Charge premium fees (high monthly packages, luxury positioning, concierge-level service).\\n- Signals of quality: polished website, multiple locations, premium amenities, corporate backing, or strong brand presence.\",\n    exclude:\"- Free, government-run, municipal, Medicaid-only, or subsidized facilities.\\n- Charity / NGO / trust-run / religious / donation-based homes.\\n- Budget, low-cost operations serving destitute or abandoned elderly.\"}\n};\nreturn items.map(item => {\n  const { city, niche, country, niche_slug } = item.json;\n  const budget = BUDGET[country] || \"a premium\";\n  const prof = PROFILES[niche_slug] || PROFILES._elder;\n  const known = (knownByCity[city.toLowerCase().trim()] || []).slice(0, 50);\n  const excludeKnown = known.length\n    ? `\\n\\nWe ALREADY know these — do NOT return any of them, find DIFFERENT ones:\\n${known.join(', ')}.`\n    : '';\n  const prompt = `Find PREMIUM \"${niche}\" businesses in ${city}, ${country}.\n\nTARGET PROFILE (only return these):\n${prof.target}\n- Businesses that could realistically afford ${budget} growth-marketing / SEO retainer.\n\nSTRICTLY EXCLUDE (do NOT return any of these):\n${prof.exclude}\n- Anything underfunded or with no real website.\n\nFind 5-10 ACTUAL companies with active websites.${excludeKnown}\n\nReturn ONLY valid JSON array: [{\"company\":\"...\",\"website\":\"...\",\"description\":\"why this fits / who they serve\"}]`;\n  return { json: { ...item.json, prompt } };\n});\n"
      },
      "id": "n_search_prompt",
      "name": "Build Search Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        288,
        96
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/responses",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4\",\"input\":$json.prompt,\"max_output_tokens\":3000} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4
            }
          },
          "timeout": 280000
        }
      },
      "id": "n_search",
      "name": "GPT Extract — Businesses",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        96
      ],
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const __ext=(j)=>j.choices?.[0]?.message?.content || ((j.output||[]).flatMap(o=>o.content||[]).filter(c=>c.type==='output_text').map(c=>c.text).join('')) || '';\n\nconst items = $input.all();\nconst sources = $('Build Search Prompt').all();\nconst results = [];\nfor (let i = 0; i < items.length; i++) {\n  const item = items[i];\n  const src = sources[i] ? sources[i].json : {};\n  const city = src.city || '';\n  const niche = src.niche || '';\n  const country = src.country || 'India';\n  const niche_slug = src.niche_slug || niche.toLowerCase().trim().replace(/\\s+/g, '_');\n  let content = __ext(item.json) || '[]';\n  const fence = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n  if (fence) content = fence[1].trim();\n  const arrMatch = content.match(/\\[[\\s\\S]*\\]/);\n  if (arrMatch) content = arrMatch[0];\n  try {\n    const companies = JSON.parse(content);\n    if (Array.isArray(companies)) {\n      for (const co of companies) {\n        const website = (co.website || co.website_url || '').toLowerCase().trim();\n        const company = co.company || co.company_name || '';\n        if (website && company) {\n          results.push({ json: { company, website, city, niche, niche_slug,\n            country, source: 'hunter_' + country.toLowerCase().replace(/\\s+/g,''),\n            description: co.description || '' } });\n        }\n      }\n    }\n  } catch(e) {}\n}\nreturn results.length ? results : [{ json: { _empty: true } }];\n"
      },
      "id": "n_parse",
      "name": "Parse Prospect Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1184,
        96
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst valid = items.filter(item => !item.json._empty && item.json.website);\nreturn valid.length ? valid : [{ json: { _none: true } }];\n"
      },
      "id": "n_filter",
      "name": "Filter Valid Prospects",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1408,
        96
      ]
    },
    {
      "parameters": {
        "jsCode": "\n// Count ONLY records actually created in Airtable (have an id); failed creates are skipped.\nconst created = $('Supabase — Add Prospects').all().filter(i => i.json && i.json.id);\nconst count = created.length;\nif (!count) return [];   // nothing truly added -> no email\nconst td='padding:6px;border:1px solid #e2e8f0;font-size:13px';\nconst th='padding:8px;border:1px solid #e2e8f0;text-align:left;background:#ebf8ff';\nconst rows = created.map(i => {\n  const f = i.json.fields || i.json;\n  const w = f.website || '';\n  return `<tr><td style=\"${td}\">${f.company||''}</td><td style=\"${td}\">${f.city||''}</td><td style=\"${td}\">${f.country||''}</td><td style=\"${td}\">${f.niche||''}</td><td style=\"${td}\"><a href=\"${w}\">${w}</a></td></tr>`;\n}).join('');\nconst html = `<html><body style=\"font-family:Arial,sans-serif;max-width:820px;margin:auto;padding:20px\">\n<h2 style=\"color:#2d3748\">Prospect Hunter — ${count} NEW prospects added to Supabase</h2>\n<p>These passed dedup and were just written to your Supabase prospects table (already-known companies skipped).</p>\n<table style=\"width:100%;border-collapse:collapse\">\n<tr><th style=\"${th}\">Company</th><th style=\"${th}\">City</th><th style=\"${th}\">Country</th><th style=\"${th}\">Niche</th><th style=\"${th}\">Website</th></tr>\n${rows}\n</table></body></html>`;\nreturn [{ json: { count, summary_html: html } }];\n"
      },
      "id": "n_summary_code",
      "name": "Build Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2304,
        0
      ]
    },
    {
      "parameters": {
        "sendTo": "you@yourdomain.com",
        "subject": "=[FYI] Prospect Hunter — {{ $json.count }} NEW prospects added to CRM",
        "message": "={{ $json.summary_html }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "n_summary_email",
      "name": "Gmail — Weekly Summary",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2528,
        0
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "prospects",
        "returnAll": true
      },
      "id": "n_fetch_existing",
      "name": "Fetch Existing Prospects",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -384,
        96
      ]
    },
    {
      "parameters": {
        "jsCode": "const existing = $('Fetch Existing Prospects').all();\nconst fresh = $input.all();\nconst normSite = (s) => { s=(s||'').toLowerCase().trim(); for (const p of ['https://','http://','www.']) if (s.startsWith(p)) s=s.slice(p.length); return s.replace(/\\/$/,''); };\nconst normName = (s) => (s||'').toLowerCase().replace(/[^a-z0-9]/g,'');\nconst seenSites = new Set(), seenNames = new Set();\nfor (const r of existing) { const f=r.json; if (f.website) seenSites.add(normSite(f.website)); if (f.company) seenNames.add(normName(f.company)); }\nconst out = [];\nfor (const p of fresh) {\n  if (p.json._empty || p.json._none) continue;\n  const site = normSite(p.json.website), name = normName(p.json.company);\n  if (!site || !name) continue;\n  if (seenSites.has(site) || seenNames.has(name)) continue;\n  seenSites.add(site); seenNames.add(name);\n  out.push({ json: p.json });\n}\nreturn out.length ? out : [{ json: { _none: true } }];\n"
      },
      "id": "n_dedup",
      "name": "Dedup Against CRM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1632,
        96
      ]
    },
    {
      "parameters": {
        "tableId": "prospects",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "company",
              "fieldValue": "={{ $json.company }}"
            },
            {
              "fieldId": "website",
              "fieldValue": "={{ $json.website }}"
            },
            {
              "fieldId": "city",
              "fieldValue": "={{ $json.city }}"
            },
            {
              "fieldId": "niche",
              "fieldValue": "={{ $json.niche_slug }}"
            },
            {
              "fieldId": "country",
              "fieldValue": "={{ $json.country }}"
            },
            {
              "fieldId": "status",
              "fieldValue": "new"
            },
            {
              "fieldId": "source",
              "fieldValue": "={{ $json.source }}"
            }
          ]
        }
      },
      "id": "n_create",
      "name": "Supabase — Add Prospects",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2080,
        0
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "scrape_coverage",
        "returnAll": true
      },
      "id": "fetch_cov",
      "name": "Fetch Coverage",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -160,
        96
      ],
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "return $input.all().filter(i=>i.json && i.json.company && i.json.website && !i.json._none);"
      },
      "id": "only_real",
      "name": "Only Real Prospects",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1856,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Record one scrape_coverage row per searched combo (exhausted if <5 new uniques).\nconst searched=$('Build City+Niche Combos').all().map(i=>i.json);\nconst uniques=$('Dedup Against CRM').all().map(i=>i.json).filter(p=>p && p.company && !p._none);\nlet raw=[]; try{ raw=$('Filter Valid Prospects').all().map(i=>i.json).filter(p=>p && p.website && !p._none); }catch(e){}\nconst key=(c,n)=>`${(c||'').toLowerCase().trim()}|${(n||'').toLowerCase().trim()}`;\nconst uCnt={}, rCnt={};\nfor(const u of uniques){ const k=key(u.city,u.niche_slug); uCnt[k]=(uCnt[k]||0)+1; }\nfor(const r of raw){ const k=key(r.city,r.niche_slug); rCnt[k]=(rCnt[k]||0)+1; }\nconst now=new Date().toISOString(); const seen=new Set(); const out=[];\nfor(const s of searched){\n  const k=key(s.city,s.niche_slug); if(seen.has(k)) continue; seen.add(k);\n  const u=uCnt[k]||0;\n  out.push({json:{ city:s.city, country:s.country, niche:s.niche, niche_slug:s.niche_slug,\n    unique_found:u, raw_found:rCnt[k]||0, exhausted:(u<5), scraped_at:now }});\n}\nreturn out;\n"
      },
      "id": "cov_rows",
      "name": "Coverage: Build Rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1856,
        192
      ]
    },
    {
      "parameters": {
        "tableId": "scrape_coverage",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "city",
              "fieldValue": "={{ $json.city }}"
            },
            {
              "fieldId": "country",
              "fieldValue": "={{ $json.country }}"
            },
            {
              "fieldId": "niche",
              "fieldValue": "={{ $json.niche }}"
            },
            {
              "fieldId": "niche_slug",
              "fieldValue": "={{ $json.niche_slug }}"
            },
            {
              "fieldId": "unique_found",
              "fieldValue": "={{ $json.unique_found }}"
            },
            {
              "fieldId": "raw_found",
              "fieldValue": "={{ $json.raw_found }}"
            },
            {
              "fieldId": "exhausted",
              "fieldValue": "={{ $json.exhausted }}"
            },
            {
              "fieldId": "scraped_at",
              "fieldValue": "={{ $json.scraped_at }}"
            }
          ]
        }
      },
      "id": "cov_rec",
      "name": "Coverage: Record",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2080,
        192
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const TAVILY_KEYS=[\"YOUR_TAVILY_API_KEY_1\",\"YOUR_TAVILY_API_KEY_2\",\"YOUR_TAVILY_API_KEY_3\"];\nconst __sd=$getWorkflowStaticData('global');\nif(typeof __sd.tavilyExhausted!=='object'||!__sd.tavilyExhausted) __sd.tavilyExhausted={};\nconst __sleep=(ms)=>new Promise(r=>setTimeout(r,ms));\nconst tavilySearch=async(query,maxResults)=>{\n  for(const k of TAVILY_KEYS){\n    const ex=__sd.tavilyExhausted[k];\n    if(ex && Date.now()-ex<24*3600*1000) continue;\n    let attempts=0;\n    while(attempts<3){\n      attempts++;\n      try{\n        const res=await this.helpers.httpRequest({method:'POST',url:'https://api.tavily.com/search',\n          headers:{'Authorization':'Bearer '+k,'Content-Type':'application/json'},\n          body:{query:String(query||'').slice(0,390),search_depth:'advanced',max_results:maxResults,include_answer:true},\n          json:true,returnFullResponse:true,ignoreHttpStatusErrors:true,timeout:90000});\n        const code=Number(res.statusCode||0);\n        if(code===429){ if(attempts<3){await __sleep(1500*attempts);continue;} break; }\n        if(code===432||code===433||code===401||code===403){ __sd.tavilyExhausted[k]=Date.now(); break; }\n        if(code<200||code>=300){ break; }\n        return res.body||{results:[]};\n      }catch(e){ break; }\n    }\n  }\n  try{ await this.helpers.httpRequest({method:'GET',url:'https://your-n8n-instance.com/webhook/yourbrand-tavily-exhausted',timeout:10000}); }catch(e){}\n  return {results:[],answer:'',tavily_error:'all_keys_exhausted'};\n};\nconst out=[];\nfor(const it of $input.all()){\n  const d=it.json||{};\n  out.push({json: await tavilySearch((\"premium luxury \" + (d.niche || \"\") + \" companies in \" + (d.city || \"\") + \" \" + (d.country || \"\")), 10)});\n  await __sleep(400);\n}\nreturn out;"
      },
      "name": "Tavily — Find Businesses",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        96
      ],
      "id": "23aede4c-e509-4f78-b784-d890ea7b8808"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst srcs = $('Build Search Prompt').all();\nreturn items.map((it, i) => {\n  const src = srcs[i] ? srcs[i].json : {};\n  const t = it.json || {};\n  const results = (t.results || []).map((r, n) => `[${n+1}] ${r.title || ''}\\nURL: ${r.url || ''}\\n${(r.content || '').slice(0, 1200)}`).join('\\n\\n');\n  const ctx = (t.answer ? `Search answer: ${t.answer}\\n\\n` : '') + (results || 'No web results found.');\n  const prompt = (src.prompt || '') + `\\n\\n---\\nWEB SEARCH RESULTS below — use these as your ONLY research data. Extract what is asked from them; do not invent facts:\\n\\n` + ctx;\n  return { json: { ...src, prompt } };\n});"
      },
      "name": "Attach Web Context — Find Businesses",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        736,
        96
      ],
      "id": "d60970c9-d2a9-44d3-bc68-90ca478a32ff"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Fetch Existing Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build City+Niche Combos": {
      "main": [
        [
          {
            "node": "Build Search Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Search Prompt": {
      "main": [
        [
          {
            "node": "Tavily — Find Businesses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT Extract — Businesses": {
      "main": [
        [
          {
            "node": "Parse Prospect Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Prospect Results": {
      "main": [
        [
          {
            "node": "Filter Valid Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Prospects": {
      "main": [
        [
          {
            "node": "Dedup Against CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Summary": {
      "main": [
        [
          {
            "node": "Gmail — Weekly Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Existing Prospects": {
      "main": [
        [
          {
            "node": "Fetch Coverage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedup Against CRM": {
      "main": [
        [
          {
            "node": "Only Real Prospects",
            "type": "main",
            "index": 0
          },
          {
            "node": "Coverage: Build Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twice Daily — 6 AM & 5 PM IST": {
      "main": [
        [
          {
            "node": "Fetch Existing Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase — Add Prospects": {
      "main": [
        [
          {
            "node": "Build Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Coverage": {
      "main": [
        [
          {
            "node": "Build City+Niche Combos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only Real Prospects": {
      "main": [
        [
          {
            "node": "Supabase — Add Prospects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Coverage: Build Rows": {
      "main": [
        [
          {
            "node": "Coverage: Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tavily — Find Businesses": {
      "main": [
        [
          {
            "node": "Attach Web Context — Find Businesses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attach Web Context — Find Businesses": {
      "main": [
        [
          {
            "node": "GPT Extract — Businesses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "binaryMode": "separate"
  }
}