{
  "name": "JS Tech Solutions: Add new business filings to Google Sheets",
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "manual-start",
      "name": "Start manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "config-mode",
              "name": "mode",
              "value": "demo",
              "type": "string"
            },
            {
              "id": "config-states",
              "name": "states",
              "value": [
                "CT"
              ],
              "type": "array"
            },
            {
              "id": "config-formedFrom",
              "name": "formedFrom",
              "value": "",
              "type": "string"
            },
            {
              "id": "config-formedTo",
              "name": "formedTo",
              "value": "",
              "type": "string"
            },
            {
              "id": "config-county",
              "name": "county",
              "value": "",
              "type": "string"
            },
            {
              "id": "config-city",
              "name": "city",
              "value": "",
              "type": "string"
            },
            {
              "id": "config-industryCodePrefix",
              "name": "industryCodePrefix",
              "value": "23",
              "type": "string"
            },
            {
              "id": "config-requireBusinessEmail",
              "name": "requireBusinessEmail",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "config-maxResults",
              "name": "maxResults",
              "value": 25,
              "type": "number"
            },
            {
              "id": "config-maxTotalChargeUsd",
              "name": "maxTotalChargeUsd",
              "value": 0.3,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "configuration",
      "name": "Edit safe user configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items=$input.all();\nif(items.length!==1 || !items[0].json || Array.isArray(items[0].json))throw new Error('Expected one configuration object');\nconst c=items[0].json;\nif(typeof c.mode!=='string'||!['demo','live'].includes(c.mode))throw new Error('mode must be demo or live');\nif(!Array.isArray(c.states)||!c.states.length)throw new Error('states must contain NY, PA or CT');\nconst states=[...new Set(c.states.map(s=>{if(typeof s!=='string'||!['NY','PA','CT'].includes(s.trim().toUpperCase()))throw new Error('states may contain only NY, PA or CT strings');return s.trim().toUpperCase()}))];\nfunction text(v,k){if(v===undefined||v===null||v==='')return null;if(typeof v!=='string')throw new Error(k+' must be text');return v.trim()||null}\nfunction date(v,k){if(v===undefined||v===null||v==='')return null;if(typeof v!=='string'||!/^\\d{4}-\\d{2}-\\d{2}$/.test(v))throw new Error(k+' must be YYYY-MM-DD text');const t=Date.parse(v+'T00:00:00.000Z');if(!Number.isFinite(t)||new Date(t).toISOString().slice(0,10)!==v)throw new Error(k+' must be a real calendar date');return v}\nconst formedFrom=date(c.formedFrom,'formedFrom'),formedTo=date(c.formedTo,'formedTo');\nif(formedFrom&&formedTo&&formedFrom>formedTo)throw new Error('formedFrom cannot be later than formedTo');\nif(typeof c.requireBusinessEmail!=='boolean')throw new Error('requireBusinessEmail must be true or false');\nif(!Number.isSafeInteger(c.maxResults)||c.maxResults<1||c.maxResults>100)throw new Error('maxResults must be an integer from 1 to 100');\nif(typeof c.maxTotalChargeUsd!=='number'||!Number.isFinite(c.maxTotalChargeUsd)||c.maxTotalChargeUsd<=0||c.maxTotalChargeUsd>0.30)throw new Error('maxTotalChargeUsd must be above 0 and no more than 0.30');\nconst industryCodePrefix=text(c.industryCodePrefix,'industryCodePrefix');\nif(industryCodePrefix&&!/^\\d{2,6}$/.test(industryCodePrefix))throw new Error('industryCodePrefix must contain 2 to 6 digits');\nif((industryCodePrefix||c.requireBusinessEmail)&&!states.includes('CT'))throw new Error('CT is required for email or NAICS filters');\nconst actorInput={states,requireBusinessEmail:c.requireBusinessEmail,maxResults:c.maxResults};\nfor(const[k,v]of Object.entries({formedFrom,formedTo,county:text(c.county,'county'),city:text(c.city,'city'),industryCodePrefix}))if(v!==null)actorInput[k]=v;\nreturn[{json:{mode:c.mode,maxResults:c.maxResults,maxTotalChargeUsd:c.maxTotalChargeUsd,actorInput}}];"
      },
      "id": "validate",
      "name": "Validate filters and spending limits",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "live-mode",
              "leftValue": "={{ $json.mode }}",
              "rightValue": "live",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "branch-live",
      "name": "Select live or demo source",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/actors/0GUHzILSSXSyN4uou/run-sync-get-dataset-items",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "timeout",
              "value": "240"
            },
            {
              "name": "maxItems",
              "value": "={{ $json.maxResults }}"
            },
            {
              "name": "maxTotalChargeUsd",
              "value": "={{ $json.maxTotalChargeUsd }}"
            },
            {
              "name": "restartOnError",
              "value": "false"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ $json.actorInput }}",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "responseFormat": "json",
              "fullResponse": false,
              "neverError": false
            }
          }
        }
      },
      "id": "actor-request",
      "name": "Run paid Apify actor with spend cap",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        180
      ],
      "disabled": true,
      "retryOnFail": false,
      "onError": "stopWorkflow"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Fictional records only. Demo ignores live filters.\nconst leads = [\n  {\n    \"leadId\": \"CT:001DEMO00001\",\n    \"state\": \"CT\",\n    \"registryId\": \"001DEMO00001\",\n    \"businessName\": \"Example Shoreline Builders LLC\",\n    \"entityType\": \"LLC\",\n    \"formationDate\": \"2026-08-28\",\n    \"status\": \"Active\",\n    \"county\": \"Fairfield\",\n    \"city\": \"Stamford\",\n    \"businessEmail\": \"hello@example.invalid\",\n    \"industryCode\": \"236220\",\n    \"decisionMakers\": [\n      {\n        \"name\": \"Morgan Example\",\n        \"role\": \"Member\"\n      }\n    ],\n    \"registeredAgents\": [\n      {\n        \"name\": \"Example Registered Agent LLC\"\n      }\n    ],\n    \"leadSignals\": [\n      \"formed_within_7_days\",\n      \"registry_published_business_email\"\n    ],\n    \"opportunityScore\": 88,\n    \"source\": \"https://example.invalid/ct-business-master\",\n    \"sourceUpdatedAt\": \"2026-08-28T09:00:00.000Z\",\n    \"attribution\": \"Connecticut Secretary of the State\",\n    \"fetchedAt\": \"2026-08-31T09:00:00.000Z\"\n  },\n  {\n    \"leadId\": \"CT:001DEMO00002\",\n    \"state\": \"CT\",\n    \"registryId\": \"001DEMO00002\",\n    \"businessName\": \"Example Masonry Co\",\n    \"entityType\": \"Corporation\",\n    \"formationDate\": \"2026-08-27\",\n    \"county\": \"New Haven\",\n    \"city\": \"New Haven\",\n    \"businessEmail\": \"contact@example.invalid\",\n    \"industryCode\": \"238140\",\n    \"decisionMakers\": [],\n    \"registeredAgents\": [],\n    \"leadSignals\": [\n      \"registry_published_industry_code\"\n    ],\n    \"opportunityScore\": 72,\n    \"source\": \"https://example.invalid/ct-business-master\",\n    \"sourceUpdatedAt\": \"2026-08-28T09:00:00.000Z\",\n    \"attribution\": \"Connecticut Secretary of the State\",\n    \"fetchedAt\": \"2026-08-31T09:00:00.000Z\"\n  }\n];\nreturn leads.map(lead => ({ json: lead }));"
      },
      "id": "raw-demo",
      "name": "Load synthetic raw demo leads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        420
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Shared by raw synthetic fixtures and native HTTP JSON items.\nconst config=$('Validate filters and spending limits').first().json;\nif(!Number.isSafeInteger(config.maxResults)||config.maxResults<1||config.maxResults>100)throw new Error('Missing validated result limit');\nconst leads=$input.all().flatMap(item=>Array.isArray(item.json)?item.json:[item.json]);\nconst out=[],seen=new Set();\nfunction safe(v){if(v==null)return '';const s=typeof v==='string'?v:JSON.stringify(v);return /^[\\s\\p{Cf}\\x00-\\x1f\\x7f-\\x9f]*[=+\\-@]/u.test(s)?\"'\"+s:s}\nfunction list(v,k){if(v==null)return [];if(!Array.isArray(v))throw new Error(k+' must be an array');return v}\nfor(const l of leads){\nif(!l||typeof l!=='object'||Array.isArray(l))throw new Error('Actor response must contain lead objects');\nif(typeof l.state!=='string'||!['NY','PA','CT'].includes(l.state))throw new Error('Invalid lead state');\nif(typeof l.registryId!=='string'||!l.registryId.trim()||/[\\x00-\\x1f\\x7f]/.test(l.registryId))throw new Error('Missing or invalid registryId');\nif(typeof l.leadId!=='string'||l.leadId!==l.state+':'+l.registryId)throw new Error('leadId must equal state:registryId');\nif(seen.has(l.leadId))continue;seen.add(l.leadId);\nconst row={leadId:l.leadId,state:l.state,businessName:l.businessName,formationDate:l.formationDate,county:l.county,city:l.city,businessEmail:l.businessEmail,industryCode:l.industryCode,entityType:l.entityType,status:l.status,\ndecisionMakers:list(l.decisionMakers,'decisionMakers').map(p=>{if(!p||typeof p!=='object'||Array.isArray(p))throw new Error('Invalid decision maker');return[p.name,p.role].filter(v=>v!=null&&v!=='').join(' · ')}).join(' | '),\nregisteredAgents:list(l.registeredAgents,'registeredAgents').map(a=>{if(!a||typeof a!=='object'||Array.isArray(a))throw new Error('Invalid registered agent');return a.name||''}).filter(Boolean).join(' | '),\nsource:l.source,sourceUpdatedAt:l.sourceUpdatedAt,attribution:l.attribution,leadSignals:list(l.leadSignals,'leadSignals').join(' | '),opportunityScore:l.opportunityScore,fetchedAt:l.fetchedAt};\nout.push({json:Object.fromEntries(Object.entries(row).map(([k,v])=>[k,safe(v)]))});\nif(out.length>=config.maxResults)break}\nreturn out;"
      },
      "id": "flatten",
      "name": "Flatten sanitize and deduplicate leads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "live-mode",
              "leftValue": "={{ $('Validate filters and spending limits').first().json.mode }}",
              "rightValue": "live",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "gate-sheet",
      "name": "Allow Sheets writes only in live mode",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1440,
        300
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Leads",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "leadId": "={{ $json.leadId }}",
            "state": "={{ $json.state }}",
            "businessName": "={{ $json.businessName }}",
            "formationDate": "={{ $json.formationDate }}",
            "county": "={{ $json.county }}",
            "city": "={{ $json.city }}",
            "businessEmail": "={{ $json.businessEmail }}",
            "industryCode": "={{ $json.industryCode }}",
            "entityType": "={{ $json.entityType }}",
            "status": "={{ $json.status }}",
            "decisionMakers": "={{ $json.decisionMakers }}",
            "registeredAgents": "={{ $json.registeredAgents }}",
            "source": "={{ $json.source }}",
            "sourceUpdatedAt": "={{ $json.sourceUpdatedAt }}",
            "attribution": "={{ $json.attribution }}",
            "leadSignals": "={{ $json.leadSignals }}",
            "opportunityScore": "={{ $json.opportunityScore }}",
            "fetchedAt": "={{ $json.fetchedAt }}"
          },
          "matchingColumns": [
            "leadId"
          ],
          "schema": [
            {
              "id": "leadId",
              "displayName": "leadId",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "state",
              "displayName": "state",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "businessName",
              "displayName": "businessName",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "formationDate",
              "displayName": "formationDate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "county",
              "displayName": "county",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "city",
              "displayName": "city",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "businessEmail",
              "displayName": "businessEmail",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "industryCode",
              "displayName": "industryCode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "entityType",
              "displayName": "entityType",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "decisionMakers",
              "displayName": "decisionMakers",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "registeredAgents",
              "displayName": "registeredAgents",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "source",
              "displayName": "source",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sourceUpdatedAt",
              "displayName": "sourceUpdatedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "attribution",
              "displayName": "attribution",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "leadSignals",
              "displayName": "leadSignals",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "opportunityScore",
              "displayName": "opportunityScore",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "fetchedAt",
              "displayName": "fetchedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "sheet-upsert",
      "name": "Upsert Google Sheets rows by leadId",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1680,
        180
      ],
      "disabled": true,
      "retryOnFail": false,
      "onError": "stopWorkflow"
    },
    {
      "parameters": {},
      "id": "demo-output",
      "name": "Inspect demo rows without external writes",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1680,
        420
      ]
    },
    {
      "parameters": {
        "content": "## New business filings to Google Sheets\n### Who this is for\nLocal service providers and sales teams building a source-backed prospect list from New York, Pennsylvania and Connecticut registrations.\n\n### How it works\nRun manually. This free template validates filters, retrieves records from the paid Apify Actor jserle/us-new-business-leads-feed, flattens 18 columns, sanitizes formula-like text, and upserts by leadId. Repeated batch keys keep the first record. Future runs update matching rows.\n\n### Setup\n1. Execute the default demo. Synthetic raw records use the live transformation but never call Apify or Google Sheets.\n2. Create a Google Sheet with a Leads tab and the 18 headers shown in the Sheets node.\n3. Select your own Apify HTTP Bearer Auth and Google Sheets credentials.\n4. Set your spreadsheet ID. Enable both external nodes, then change mode to live.\n\n### Requirements and limits\nRequires n8n, Google Sheets access and an Apify account with paid Actor access. The template is free; live Actor results cost money. Defaults request at most 25 CT construction registrations with published business email. The $0.30 charge cap applies per run, not monthly. No schedule or outreach is included. Demo ignores filters. Blank dates use Actor defaults. Email and NAICS filters limit results to CT. Review source freshness and permitted use before contacting anyone.",
        "height": 650,
        "width": 660,
        "color": 1
      },
      "id": "intro-note",
      "name": "Read setup and paid dependency",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -20,
        -410
      ]
    },
    {
      "parameters": {
        "content": "### 1. Choose filters\nEdit the Set Fields node. Optional dates use YYYY-MM-DD. Clear NAICS and set requireBusinessEmail=false to include NY/PA. Hard limits: 100 results, $0.30 per run. Invalid inputs stop before a request.",
        "height": 250,
        "width": 400,
        "color": 6
      },
      "id": "config-note",
      "name": "Configure filters note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        700,
        -160
      ]
    },
    {
      "parameters": {
        "content": "### 2. Connect live services\nBoth external nodes are disabled on import. Select your credentials and enable both before selecting live mode. Never paste credentials into URLs or Code nodes. Requests stop on errors, have no retries, and use a 240-second server timeout.",
        "height": 250,
        "width": 410,
        "color": 6
      },
      "id": "source-note",
      "name": "Select credentials note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1140,
        -160
      ]
    },
    {
      "parameters": {
        "content": "### 3. Review output\nCreate the 18 matching headers in Leads. Upsert matches leadId with RAW cell formatting. Use a dedicated tab without existing duplicate leadId values and avoid concurrent runs. Demo ends in Inspect demo rows with no external writes.",
        "height": 250,
        "width": 410,
        "color": 6
      },
      "id": "output-note",
      "name": "Prepare destination note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1580,
        -160
      ]
    }
  ],
  "connections": {
    "Start manually": {
      "main": [
        [
          {
            "node": "Edit safe user configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit safe user configuration": {
      "main": [
        [
          {
            "node": "Validate filters and spending limits",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate filters and spending limits": {
      "main": [
        [
          {
            "node": "Select live or demo source",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select live or demo source": {
      "main": [
        [
          {
            "node": "Run paid Apify actor with spend cap",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Load synthetic raw demo leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run paid Apify actor with spend cap": {
      "main": [
        [
          {
            "node": "Flatten sanitize and deduplicate leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load synthetic raw demo leads": {
      "main": [
        [
          {
            "node": "Flatten sanitize and deduplicate leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten sanitize and deduplicate leads": {
      "main": [
        [
          {
            "node": "Allow Sheets writes only in live mode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Allow Sheets writes only in live mode": {
      "main": [
        [
          {
            "node": "Upsert Google Sheets rows by leadId",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Inspect demo rows without external writes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "tags": []
}
