{
  "id": "7z3tSI3Nr3nosLV2",
  "name": "Verify Polish VAT taxpayers (Bia\u0142a lista) from Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "f28aad5c-d64d-4104-8af1-d71887ccd195",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -160
      ],
      "parameters": {
        "width": 480,
        "height": 864,
        "content": "## Verify Polish VAT taxpayers (Bia\u0142a lista) from Google Sheets\n\n### How it works\n\nThis workflow verifies Polish VAT taxpayers from a Google Sheets contractor list against the Ministry of Finance Bia\u0142a lista API. It can be started manually or on a weekday schedule, reads contractor data, normalizes and validates NIP numbers, calls the API, and turns the response into a plain verdict.\n\n### Setup steps\n\n- Configure Google Sheets credentials for the Read contractors node and select the spreadsheet/range containing contractor data.\n- Update the Settings node with the correct whiteListApi endpoint and the column names for NIP, contractor name, and bank account.\n- Confirm the weekday schedule time and timezone on the Every weekday 7:00 trigger, or use the manual trigger for ad hoc checks.\n- Review the Prepare NIPs and Evaluate results code nodes if your sheet uses different formatting or you need different output fields.\n\n### Customization\n\nAdjust the Settings node to point to different sheet columns or API environments, change the schedule frequency, or extend the evaluation code to write results back to Google Sheets, send alerts, or include bank-account matching."
      },
      "typeVersion": 1
    },
    {
      "id": "58b29a33-d9ee-4ca4-b06c-418026fd00eb",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 496,
        "content": "## Start and configure\n\nContains the manual and weekday scheduled triggers, then centralizes runtime settings such as the Bia\u0142a lista API URL and spreadsheet column names."
      },
      "typeVersion": 1
    },
    {
      "id": "3f707231-6f1b-4b48-ae65-8f83f4bc5f2b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Read and prepare NIPs\n\nReads contractor rows from Google Sheets and normalizes the NIP values from the configured column, including checksum validation before lookup."
      },
      "typeVersion": 1
    },
    {
      "id": "4d4efb7c-b9d6-4913-b85e-8209ef87db48",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Check and evaluate taxpayers\n\nQueries the Ministry of Finance white list API for each prepared NIP and converts the response into a clear taxpayer verification verdict."
      },
      "typeVersion": 1
    },
    {
      "id": "1e04ee2a-f68a-4b4f-9af6-f0cc866ded05",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        864,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "915efa21-7453-4a57-8766-07401849272b",
      "name": "Schedule Weekdays at 7am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        864,
        176
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "12cde684-d93a-4eb3-ae19-252a2575b98c",
      "name": "Set API and Columns",
      "type": "n8n-nodes-base.set",
      "position": [
        1072,
        96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0",
              "name": "whiteListApi",
              "type": "string",
              "value": "https://wl-api.mf.gov.pl/api/search/nip/"
            },
            {
              "id": "1",
              "name": "nipColumn",
              "type": "string",
              "value": "NIP"
            },
            {
              "id": "2",
              "name": "nameColumn",
              "type": "string",
              "value": "Company"
            },
            {
              "id": "3",
              "name": "accountColumn",
              "type": "string",
              "value": "Bank account"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d5dceeb9-c10c-426e-a4a5-9790056e95db",
      "name": "Read Contractors from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "notes": "Select your spreadsheet and worksheet here.",
      "position": [
        1296,
        96
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "bc02ed65-7f8e-4f79-884e-9f6935ca1e47",
      "name": "Normalize and Validate NIPs",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        96
      ],
      "parameters": {
        "jsCode": "// Normalise NIP numbers coming from the spreadsheet and validate the checksum.\n// A malformed NIP is rejected locally, so we never waste an API call on it.\nconst cfg = $('Set API and Columns').first().json;\nconst column = cfg.nipColumn || 'NIP';\n\nfunction checksumOk(nip) {\n  if (!/^\\d{10}$/.test(nip)) return false;\n  const w = [6, 5, 7, 2, 3, 4, 5, 6, 7];\n  const sum = w.reduce((acc, weight, i) => acc + weight * Number(nip[i]), 0);\n  return sum % 11 === Number(nip[9]);\n}\n\nconst out = [];\nfor (const item of $input.all()) {\n  const row = item.json;\n  const raw = String(row[column] ?? '').replace(/[^0-9]/g, '');\n  out.push({\n    json: {\n      ...row,\n      nip: raw,\n      nip_valid_format: checksumOk(raw),\n      row_number: row.row_number ?? null,\n    },\n  });\n}\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "69059b30-1e02-4d5f-8f7e-d3c301ab7b1a",
      "name": "Fetch VAT White List Status",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1760,
        96
      ],
      "parameters": {
        "url": "={{ $('Set API and Columns').first().json.whiteListApi }}{{ $json.nip }}?date={{ $now.format('yyyy-MM-dd') }}",
        "options": {
          "timeout": 20000,
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 1200
            }
          }
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "025266f5-e27f-4813-9a47-fff5027ac38e",
      "name": "Determine VAT Compliance Status",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        96
      ],
      "parameters": {
        "jsCode": "// Turn the Ministry of Finance response into a plain verdict.\n// The API answers with { result: { subject: {...} } } \u2014 an ACTIVE taxpayer has\n// statusVat = \"Czynny\". Anything else means: do not pay yet.\nconst cfg = $('Set API and Columns').first().json;\nconst inputs = $('Normalize and Validate NIPs').all().map(i => i.json);\nconst results = $input.all().map(i => i.json);\n\nconst rows = [];\nfor (let i = 0; i < inputs.length; i++) {\n  const src = inputs[i];\n  const res = results[i] || {};\n  const subject = res?.result?.subject || null;\n\n  let verdict, detail;\n  if (!src.nip_valid_format) {\n    verdict = 'INVALID_NIP';\n    detail = 'Checksum failed \u2014 the number is not a valid Polish NIP.';\n  } else if (!subject) {\n    verdict = 'NOT_FOUND';\n    detail = 'No taxpayer found for this NIP on the given date.';\n  } else if (subject.statusVat === 'Czynny') {\n    verdict = 'ACTIVE';\n    detail = subject.name || '';\n  } else {\n    verdict = 'NOT_ACTIVE';\n    detail = `statusVat = ${subject.statusVat || 'unknown'}`;\n  }\n\n  const accounts = Array.isArray(subject?.accountNumbers) ? subject.accountNumbers : [];\n  const expected = String(src[cfg.accountColumn || 'Bank account'] || '').replace(/[^0-9]/g, '');\n  const accountOk = !expected ? null : accounts.some(a => String(a).replace(/[^0-9]/g, '') === expected);\n\n  rows.push({ json: {\n    nip: src.nip,\n    company: subject?.name || src[cfg.nameColumn || 'Company'] || '',\n    verdict,\n    detail,\n    account_on_white_list: accountOk,\n    registration_legal_date: subject?.registrationLegalDate || null,\n    checked_at: new Date().toISOString(),\n    row_number: src.row_number,\n  }});\n}\n\nconst problems = rows.filter(r => r.json.verdict !== 'ACTIVE' || r.json.account_on_white_list === false);\nif (problems.length) {\n  rows.unshift({ json: {\n    summary: true,\n    checked: rows.length,\n    problems: problems.length,\n    message: `${problems.length} of ${rows.length} contractors need attention before payment.`,\n  }});\n}\nreturn rows;"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "0a3b0d55-f03a-49b5-9d1e-6be93f417f48",
  "nodeGroups": [],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set API and Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set API and Columns": {
      "main": [
        [
          {
            "node": "Read Contractors from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Weekdays at 7am": {
      "main": [
        [
          {
            "node": "Set API and Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch VAT White List Status": {
      "main": [
        [
          {
            "node": "Determine VAT Compliance Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize and Validate NIPs": {
      "main": [
        [
          {
            "node": "Fetch VAT White List Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Contractors from Sheets": {
      "main": [
        [
          {
            "node": "Normalize and Validate NIPs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}