{
  "name": "UK company enrichment from Companies House (NexGen Signal)",
  "nodes": [
    {
      "parameters": {},
      "id": "8001dcdd-3f6a-43c4-9bdb-5d536c24195c",
      "name": "Run now (manual)",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -220,
        220
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Companies to check"
        },
        "options": {}
      },
      "id": "df643a85-dd37-4c1c-934d-c416820c41fc",
      "name": "Read companies to check",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        0,
        220
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Enriched"
        },
        "options": {}
      },
      "id": "7b21c41e-2f19-45de-8173-2abcac958829",
      "name": "Read enriched rows",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        220,
        220
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Google Sheets account"
        }
      },
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "\nconst unknownMessage = (at) => `START OUTCOME UNKNOWN: a request to start a paid run was sent at ${at} but no answer `\n  + `came back, so it is not known whether Apify started it. This workflow will not start another run until you decide. `\n  + `Check Apify Console > Runs for nexgensignal/uk-company-status-records around that time. If a run is there, paste its run ID into `\n  + `resumeRunId in Settings (its results are collected without paying again). If no run started, paste ${at} into `\n  + `startNewAfterUnknown.`;\n// Settings: edit, then save. Each company delivered is charged (no start fee); numbers not found are free.\nconst PER_NAME = 5;                 // most register matches paid for per name searched\nconst CAP_USD = 2.00;               // hard spending cap for each of the two runs\nconst recovery = {\n  resumeRunIds: { numbers: '', names: '' },   // after START OUTCOME UNKNOWN: run IDs found in Apify Console\n  startNewAfterUnknown: '',                   // or paste the time shown, if you checked no run started\n};\nconst NUMBER = /^(?:[A-Z]{2}\\d{6}|\\d{1,8})$/;\nconst memory = $getWorkflowStaticData('global');\nconst inputFor = (kind, lookups) => kind === 'numbers'\n  ? { companyNumbers: lookups.map(w => w.toUpperCase().replace(/\\s/g, '')), maxRecords: lookups.length }\n  : { searchQueries: lookups, maxRecords: PER_NAME * lookups.length };\nmemory.usedResumeIds = memory.usedResumeIds || [];\nfor (const kind of ['numbers', 'names']) {        // recovery: collect runs you found in Apify Console\n  const id = recovery.resumeRunIds[kind];\n  if (id && !memory.usedResumeIds.includes(id)) {\n    memory.usedResumeIds.push(id);\n    const planned = memory.startAttempt && memory.startAttempt.plan ? memory.startAttempt.plan.find(p => p.kind === kind) : null;\n    memory.pendingRuns = (memory.pendingRuns || []).filter(p => p.kind !== kind)\n      .concat([{ kind, lookups: planned ? planned.lookups : null, runId: id }]);\n    if (memory.startAttempt && memory.startAttempt.plan) memory.startAttempt.plan = memory.startAttempt.plan.filter(p => p.kind !== kind);\n  }\n}\nif (memory.startAttempt && (!memory.startAttempt.plan || !memory.startAttempt.plan.length)) memory.startAttempt = null;\nif (memory.startAttempt) {                        // a start whose outcome is still unknown\n  if (recovery.startNewAfterUnknown !== memory.startAttempt.at) throw new Error(unknownMessage(memory.startAttempt.at));\n  memory.startAttempt = null;\n}\nif (memory.pendingRuns && memory.pendingRuns.length)      // finish runs already started: no new charge\n  return memory.pendingRuns.map(p => ({ json: { mode: 'resume', runId: p.runId, kind: p.kind, lookups: p.lookups,\n                                                perName: PER_NAME } }));\nconst done = new Set($('Read enriched rows').all().map(i => String(i.json.lookup || '')));\nconst wanted = [...new Set($('Read companies to check').all().map(i => String(i.json.lookup || '').trim()).filter(Boolean))];\nconst todo = wanted.filter(w => !done.has(w));    // already enriched rows are skipped (delete a row to re-check it)\nconst numbers = todo.filter(w => NUMBER.test(w.toUpperCase().replace(/\\s/g, '')));\nconst names = todo.filter(w => !numbers.includes(w));\n// Numbers and names go in two runs: within one run the Actor combines its filters (number AND name).\nconst out = [];\nfor (const [kind, lookups] of [['numbers', numbers], ['names', names]])\n  if (lookups.length) out.push({ json: { mode: 'start', kind, lookups, perName: PER_NAME, maxTotalChargeUsd: CAP_USD,\n                                         actorInput: inputFor(kind, lookups) } });\nreturn out;                         // nothing new to check -> the workflow stops here, nothing charged\n"
      },
      "id": "b69d0b02-a118-4ef7-b8b8-4a182ff4caf3",
      "name": "Plan runs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "6fcbc515-50d5-40e6-99e6-5f0a6ebb4315",
              "leftValue": "={{ $json.mode === 'resume' }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "019d3d52-6c2f-4320-a4ff-714b0e9613d3",
      "name": "Resume a saved run?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        660,
        220
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/actor-runs/{{ $json.runId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        }
      },
      "id": "e4e174cb-2cde-4521-953f-3d46696608a1",
      "name": "Get saved run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        80
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "const memory = $getWorkflowStaticData('global');\nmemory.startAttempt = { at: new Date().toISOString(), plan: $input.all().map(i => ({ kind: i.json.kind, lookups: i.json.lookups })) };\nreturn $input.all();"
      },
      "id": "1459984e-ad12-455a-a494-4d59af226a2c",
      "name": "Record start attempt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        280
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/acts/nexgensignal~uk-company-status-records/runs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            },
            {
              "name": "maxTotalChargeUsd",
              "value": "={{ $json.maxTotalChargeUsd }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.actorInput) }}"
      },
      "id": "8c86152e-920e-4e85-a822-89ebcfdffadc",
      "name": "Start Apify run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        280
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "const memory = $getWorkflowStaticData('global');\nmemory.pendingRuns = memory.pendingRuns || [];\n$input.all().forEach((it, i) => { const plan = $('Record start attempt').itemMatching(i).json;\n  memory.pendingRuns.push({ kind: plan.kind, lookups: plan.lookups, runId: it.json.data.id });\n  if (memory.startAttempt) memory.startAttempt.plan = memory.startAttempt.plan.filter(p => p.kind !== plan.kind); });\nif (memory.startAttempt && !memory.startAttempt.plan.length) memory.startAttempt = null;\nreturn $input.all();"
      },
      "id": "b6fc47b7-c551-4a43-b1c5-077c40acb359",
      "name": "Remember run ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst unknownMessage = (at) => `START OUTCOME UNKNOWN: a request to start a paid run was sent at ${at} but no answer `\n  + `came back, so it is not known whether Apify started it. This workflow will not start another run until you decide. `\n  + `Check Apify Console > Runs for nexgensignal~uk-company-status-records around that time. If a run is there, paste its run ID into `\n  + `resumeRunId in Settings (its results are collected without paying again). If no run started, paste ${at} into `\n  + `startNewAfterUnknown.`;\n\nconst memory = $getWorkflowStaticData('global');\nconst e = $json.error || {};\nconst status = Number(e.httpCode || e.status || (String(e.message || '').match(/\\b([45]\\d\\d)\\b/) || [])[1] || 0);\nif (status >= 400 && status < 500) {          // Apify answered with a refusal: no run was started\n  if (memory.startAttempt && memory.startAttempt.plan) {\n    let kind = null; try { kind = $('Record start attempt').itemMatching(0).json.kind; } catch (err) {}\n    memory.startAttempt.plan = memory.startAttempt.plan.filter(p => p.kind !== kind);\n    if (!memory.startAttempt.plan.length) memory.startAttempt = null;\n  } else { memory.startAttempt = null; }\n  return [{ json: { message: `Apify refused to start the run (HTTP ${status}): ${e.message || ''} Nothing was started.` } }];\n}\n// (no throw here: n8n keeps changes to saved memory only when the node itself succeeds; the next node stops)\nreturn [{ json: { message: unknownMessage(memory.startAttempt ? memory.startAttempt.at : 'unknown time') } }];\n"
      },
      "id": "a280ef33-c659-4f43-a337-f8d471bf9589",
      "name": "Start outcome unknown",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        420
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ $json.message }}"
      },
      "id": "cbdc33a1-8c7c-4a97-9075-9a49bf928a44",
      "name": "Stop: start problem",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1540,
        420
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "765cfaff-95ef-42de-8254-5a13ec83913e",
              "leftValue": "={{ $input.all().some(i => ['READY','RUNNING','TIMING-OUT','ABORTING'].includes(i.json.data.status)) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0fb4f60-52ea-476d-bdee-02f6404fd698",
      "name": "Still running?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1540,
        220
      ]
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      },
      "id": "e498f33f-cfda-4259-852a-adfbe8a375fc",
      "name": "Wait 5 s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1760,
        60
      ],
      "webhookId": "8e79040f-7554-421d-9877-872e57f0a71c"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/actor-runs/{{ $json.data.id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            }
          ]
        }
      },
      "id": "b816887a-ac9b-45b2-8aed-76c786615012",
      "name": "Check run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        60
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {},
      "id": "bd7b2a5d-3581-4177-8eab-6e0daf91c767",
      "name": "Run finished",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1760,
        280
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.apify.com/v2/acts/nexgensignal~uk-company-status-records/runs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "desc",
              "value": "1"
            },
            {
              "name": "limit",
              "value": "25"
            }
          ]
        }
      },
      "id": "6f907f67-95f5-4d8e-baf7-d6e60cbf93ef",
      "name": "List runs of this Actor",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        770,
        520
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "executeOnce": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "\n// Runs of this Actor that are still going (READY / RUNNING / TIMING-OUT), newest first.\nconst going = ['READY', 'RUNNING', 'TIMING-OUT'];\nconst items = (($json.data || {}).items || []).filter(r => going.includes(r.status));\nif (!items.length) return [{ json: { none: true } }];\nreturn items.map(r => ({ json: { runId: r.id, kv: r.defaultKeyValueStoreId, startedAt: r.startedAt } }));\n"
      },
      "id": "fb7319ec-1ca8-4667-b423-341d6a9fd129",
      "name": "Runs still going",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        990,
        520
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "9ec27521-49e3-42cf-bce0-844fbfa0ef34",
              "leftValue": "={{ !$json.none }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a2674564-00e0-4c9a-b041-49aca16f7acd",
      "name": "Another run going?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1210,
        520
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/key-value-stores/{{ $json.kv }}/records/INPUT",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        }
      },
      "id": "c5c6498d-bf20-469c-9361-c07747641a04",
      "name": "Read input of running runs",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1430,
        460
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "alwaysOutputData": true,
      "onError": "continueRegularOutput",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "\n// The run this execution would start: the 'start' items planned in 'Plan runs'.\nconst planned = $('Plan runs').all().map(it => ({ json: it.json })).filter(x => x.json.mode === 'start');\nlet running = [];\ntry { running = $('Read input of running runs').all().map(i => i.json || {}); } catch (e) { running = []; }\nlet ids = [];\ntry { ids = $('Runs still going').all().map(i => i.json); } catch (e) { ids = []; }\n// Same settings = every setting this execution would send has the same value in the running run's input\n// (Apify may add the Actor's defaults to a run's stored input, so extra fields there do not matter).\nconst same = (mine, theirs) => Object.keys(mine).every(k => JSON.stringify(mine[k]) === JSON.stringify(theirs[k]));\nfor (const p of planned) {\n  const i = running.findIndex(inp => inp && typeof inp === 'object' && same(p.json.actorInput || {}, inp));\n  if (i >= 0) {\n    const r = ids[i] || {};\n    return [{ json: { __overlap: `ALREADY RUNNING: run ${r.runId || ''} of this Actor, started ${r.startedAt || ''} with `\n      + `the same settings, is still going (most likely started by an earlier execution of this workflow that is `\n      + `still waiting for it). This execution stopped without starting or paying for anything; the earlier `\n      + `execution writes those results. If that earlier execution was stopped, its run is not collected `\n      + `automatically: paste its run ID into resumeRunId in Settings.` } }];\n  }\n}\nreturn planned;\n"
      },
      "id": "2b998a38-ca16-48d3-8333-af7ffa30a773",
      "name": "Same settings already running?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1650,
        520
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "91c9a2e9-df26-41e5-829b-c4cf5553d318",
              "leftValue": "={{ !!$json.__overlap }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0a07cf8a-90f6-4fe1-b44a-ed749269db8d",
      "name": "Overlap?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1870,
        520
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ $json.__overlap }}"
      },
      "id": "3c462aae-d998-4c2a-875c-5edbf0e26059",
      "name": "Stop: already running",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2090,
        460
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/key-value-stores/{{ $json.data.defaultKeyValueStoreId }}/records/RUN_RECEIPT",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        }
      },
      "id": "b8bb4275-71a3-41dc-b516-ce1c168b8bdf",
      "name": "Get run receipt",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        280
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "alwaysOutputData": true,
      "onError": "continueRegularOutput",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/datasets/{{ $('Run finished').item.json.data.defaultDatasetId }}/items",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "clean",
              "value": "true"
            },
            {
              "name": "format",
              "value": "json"
            }
          ]
        }
      },
      "id": "7b8c0632-0b59-4784-838a-4aadd5c52db4",
      "name": "Get records",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        280
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.apify.com/v2/key-value-stores/{{ $('Run finished').item.json.data.defaultKeyValueStoreId }}/records/INPUT",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 120000
        }
      },
      "id": "a7606c79-1e0e-46e3-880f-44f31927a88a",
      "name": "Get run input",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2090,
        440
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Apify API token"
        }
      },
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "\nconst norm = s => String(s || '').toUpperCase().replace(/&/g, ' AND ').replace(/\\bPUBLIC LIMITED COMPANY\\b/g, 'PLC')\n  .replace(/\\bLIMITED\\b/g, 'LTD').replace(/[^A-Z0-9]+/g, '');\nconst pad = n => /^\\d+$/.test(n) ? n.padStart(8, '0') : n;\nconst s = v => v === null || v === undefined ? '' : String(v);\nconst now = new Date().toISOString();\nconst memory = $getWorkflowStaticData('global');\nconst runs = $('Run finished').all().map(i => i.json.data);\nconst perName = $('Plan runs').first().json.perName;\n// The saved run list says which lookups each finished run answers (a run whose start was uncertain is not here).\nconst inputs = $('Get run input').all().map(i => i.json || {});\nconst plans = runs.map((r, i) => {\n  const saved = (memory.pendingRuns || []).find(p => p.runId === r.id) || {};\n  const inp = inputs[i] || {};\n  const kind = saved.kind || (inp.companyNumbers ? 'numbers' : 'names');\n  const lookups = saved.lookups || (kind === 'numbers' ? inp.companyNumbers : inp.searchQueries) || [];\n  return { kind, lookups, runId: r.id, perName };\n});\nconst failed = runs.filter(r => r.status !== 'SUCCEEDED' || (r.statusMessage || '').startsWith('SOURCE UNAVAILABLE'));\nif (failed.length) {                        // nothing usable to reuse: forget these runs, then stop (next node)\n  memory.pendingRuns = (memory.pendingRuns || []).filter(p => !failed.some(r => r.id === p.runId));\n  return [{ json: { __stop: 'Apify run did not complete: ' + failed.map(r => `${r.id} ${r.status} ${r.statusMessage || ''}`).join(' | ') } }];\n}\nconst receipts = $('Get run receipt').all().map(i => i.json || {});\nconst notFound = new Set(receipts.flatMap(r => r.company_numbers_not_on_register || []).map(pad));\nconst companies = $('Get records').all().map(i => i.json).filter(c => c && c.company_number);\nconst capped = runs.some(r => /^(CAPPED|PARTIAL)/.test(r.statusMessage || ''));\nconst fields = c => ({ company_number: s(c.company_number), company_name: s(c.company_name),\n  company_status: s(c.company_status), company_category: s(c.company_category), incorporation_date: s(c.incorporation_date),\n  registered_address: [c.reg_address_line1, c.reg_address_line2].filter(Boolean).join(', '),\n  reg_post_town: s(c.reg_post_town), reg_postcode: s(c.reg_postcode), sic_codes: s(c.sic_codes),\n  accounts_next_due: s(c.accounts_next_due), accounts_last_made_up: s(c.accounts_last_made_up),\n  conf_stmt_next_due: s(c.conf_stmt_next_due),\n  previous_names: Array.isArray(c.previous_names) ? c.previous_names.map(p => p.name || String(p)).join('; ') : s(c.previous_names),\n  companies_house_url: s(c.companies_house_url), snapshot_date: s(c.snapshot_date) });\nconst blank = Object.fromEntries([\"lookup\", \"lookup_type\", \"result\", \"company_number\", \"company_name\", \"company_status\", \"company_category\", \"incorporation_date\", \"registered_address\", \"reg_post_town\", \"reg_postcode\", \"sic_codes\", \"accounts_next_due\", \"accounts_last_made_up\", \"conf_stmt_next_due\", \"previous_names\", \"other_matches\", \"companies_house_url\", \"snapshot_date\", \"checked_at\"].map(c => [c, '']));\nconst out = [];\nfor (const plan of plans) {\n  for (const w of plan.lookups) {\n    const row = { ...blank, lookup: String(w), lookup_type: plan.kind === 'numbers' ? 'number' : 'name', checked_at: now };\n    if (plan.kind === 'numbers') {\n      const n = pad(String(w).toUpperCase().replace(/\\s/g, ''));\n      const c = companies.find(x => x.company_number === n);\n      if (c) Object.assign(row, fields(c), { result: 'found' });\n      else row.result = notFound.has(n) || !capped ? 'not_on_register' : 'not_checked_limit_reached';\n    } else {\n      const hits = companies.filter(c => String(c.match || '').split(';').some(m => m.trim().toLowerCase() === 'name: ' + String(w).toLowerCase()));\n      const exact = hits.filter(c => norm(c.company_name) === norm(w));\n      row.other_matches = hits.filter(c => c !== exact[0]).map(c => `${c.company_name} (${c.company_number})`).join('; ');\n      if (exact.length === 1) Object.assign(row, fields(exact[0]), { result: 'found_exact_name' });\n      else if (exact.length > 1) row.result = 'several_exact_names';\n      else if (hits.length) row.result = 'no_exact_name';\n      else row.result = (capped || companies.filter(c => String(c.match || '').startsWith('name:')).length >= plan.perName * plan.lookups.length) ? 'not_checked_limit_reached' : 'no_match';\n    }\n    out.push({ json: row });\n  }\n}\nreturn out;\n"
      },
      "id": "fa412647-f1fb-49dc-988f-116a54a439a9",
      "name": "Match results to lookups",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2420,
        280
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Enriched"
        },
        "operation": "appendOrUpdate",
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "lookup"
          ],
          "schema": [
            {
              "id": "lookup",
              "displayName": "lookup",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "lookup_type",
              "displayName": "lookup_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "result",
              "displayName": "result",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "company_number",
              "displayName": "company_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "company_name",
              "displayName": "company_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "company_status",
              "displayName": "company_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "company_category",
              "displayName": "company_category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "incorporation_date",
              "displayName": "incorporation_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "registered_address",
              "displayName": "registered_address",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "reg_post_town",
              "displayName": "reg_post_town",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "reg_postcode",
              "displayName": "reg_postcode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sic_codes",
              "displayName": "sic_codes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "accounts_next_due",
              "displayName": "accounts_next_due",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "accounts_last_made_up",
              "displayName": "accounts_last_made_up",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "conf_stmt_next_due",
              "displayName": "conf_stmt_next_due",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "previous_names",
              "displayName": "previous_names",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "other_matches",
              "displayName": "other_matches",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "companies_house_url",
              "displayName": "companies_house_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "snapshot_date",
              "displayName": "snapshot_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "checked_at",
              "displayName": "checked_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "e7f88398-9bc8-4e7c-8b4a-646af0c72eaf",
      "name": "Enriched (sheet)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2640,
        280
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "\n// Runs only after the result rows were written: forget the runs whose results were saved.\nconst memory = $getWorkflowStaticData('global');\nconst done = new Set($('Run finished').all().map(i => i.json.data.id));\nmemory.pendingRuns = (memory.pendingRuns || []).filter(p => !done.has(p.runId));\nif (!memory.pendingRuns.length) memory.pendingRuns = null;\nreturn [{ json: { saved: $input.all().length } }];\n"
      },
      "id": "86be6c27-a363-4014-9e41-1e0cb041d40c",
      "name": "Save progress",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        280
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "f60eb425-cbd4-4fb4-ad3e-9d5d6219a4fa",
              "leftValue": "={{ !!$json.__stop }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4b94fe7c-ec2e-4e7c-861d-41937ccd99e1",
      "name": "Run problem?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2530,
        120
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ $json.__stop }}"
      },
      "id": "f3bf7c6a-4820-4afb-97c4-7cad0ae70ff5",
      "name": "Stop: run failed",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2750,
        60
      ]
    },
    {
      "parameters": {
        "content": "## UK company enrichment (Companies House register)\nPut one company per row in the **Companies to check** tab, column `lookup` (format that column as Plain text so numbers like `00445790` keep their zeros): a company number or a company name. Each execution enriches only rows not yet in the **Enriched** tab, via **nexgensignal/uk-company-status-records**.\n\n1. Create an **Apify API token** credential: *Header Auth*, name `Authorization`, value `Bearer <your token>`; select it in the eight HTTP Request nodes.\n2. Make the two tabs and paste the header rows from the tutorial; paste the sheet URL into the three sheet nodes.\n3. Click *Execute workflow*. A run reads the whole monthly register file: allow about 2\u20133 minutes.\n\nCost: $0.05 per company delivered on the Free plan ($0.045 Bronze, $0.04 Silver, $0.0335 Gold+), no start fee; numbers not on the register are free. A name can match several companies: you pay for up to 5 per name. Runs whose results were not written are reused next time instead of paying again.",
        "height": 400,
        "width": 600,
        "color": 4
      },
      "id": "a588b64a-93bc-4d0e-92b0-ae9004e51f22",
      "name": "Note 81c933",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        -320
      ]
    }
  ],
  "connections": {
    "Run now (manual)": {
      "main": [
        [
          {
            "node": "Read companies to check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read companies to check": {
      "main": [
        [
          {
            "node": "Read enriched rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read enriched rows": {
      "main": [
        [
          {
            "node": "Plan runs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start outcome unknown": {
      "main": [
        [
          {
            "node": "Stop: start problem",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Plan runs": {
      "main": [
        [
          {
            "node": "Resume a saved run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resume a saved run?": {
      "main": [
        [
          {
            "node": "Get saved run",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "List runs of this Actor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List runs of this Actor": {
      "main": [
        [
          {
            "node": "Runs still going",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Runs still going": {
      "main": [
        [
          {
            "node": "Another run going?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Another run going?": {
      "main": [
        [
          {
            "node": "Read input of running runs",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Same settings already running?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read input of running runs": {
      "main": [
        [
          {
            "node": "Same settings already running?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Same settings already running?": {
      "main": [
        [
          {
            "node": "Overlap?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Overlap?": {
      "main": [
        [
          {
            "node": "Stop: already running",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Record start attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Record start attempt": {
      "main": [
        [
          {
            "node": "Start Apify run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Apify run": {
      "main": [
        [
          {
            "node": "Remember run ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Start outcome unknown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get saved run": {
      "main": [
        [
          {
            "node": "Still running?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remember run ID": {
      "main": [
        [
          {
            "node": "Still running?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still running?": {
      "main": [
        [
          {
            "node": "Wait 5 s",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Run finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 5 s": {
      "main": [
        [
          {
            "node": "Check run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check run": {
      "main": [
        [
          {
            "node": "Still running?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run finished": {
      "main": [
        [
          {
            "node": "Get run receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get run receipt": {
      "main": [
        [
          {
            "node": "Get run input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get run input": {
      "main": [
        [
          {
            "node": "Get records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get records": {
      "main": [
        [
          {
            "node": "Match results to lookups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match results to lookups": {
      "main": [
        [
          {
            "node": "Run problem?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run problem?": {
      "main": [
        [
          {
            "node": "Stop: run failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Enriched (sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enriched (sheet)": {
      "main": [
        [
          {
            "node": "Save progress",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "America/New_York"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}