{
  "name": "Validate e-invoices from a web form",
  "nodes": [
    {
      "parameters": {
        "formTitle": "E-Invoice Validator",
        "formDescription": "Upload an e-invoice file (XRechnung, ZUGFeRD, Factur-X, UBL) to validate it against EU standards.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Invoice File",
              "fieldType": "file",
              "multipleFiles": false,
              "acceptFileTypes": ".xml,.pdf",
              "requiredField": true
            },
            {
              "fieldLabel": "Country",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "DE - Germany"
                  },
                  {
                    "option": "AT - Austria"
                  },
                  {
                    "option": "CH - Switzerland"
                  },
                  {
                    "option": "FR - France"
                  },
                  {
                    "option": "IT - Italy"
                  },
                  {
                    "option": "ES - Spain"
                  },
                  {
                    "option": "NL - Netherlands"
                  },
                  {
                    "option": "BE - Belgium"
                  },
                  {
                    "option": "PL - Poland"
                  }
                ]
              },
              "requiredField": true
            }
          ]
        },
        "options": {}
      },
      "id": "e1a8c4d0-d79f-4aa6-a758-25d1f42b8b71",
      "name": "Upload Form",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.5,
      "position": [
        3648,
        1808
      ],
      "webhookId": "e9f273b5-df83-468d-aa21-02d376100fa9"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const country = $json['Country'] || 'DE - Germany';\nconst countryCode = country.split(' - ')[0].trim();\nconst binary = $input.item.binary || {};\nconst firstKey = Object.keys(binary)[0];\nreturn {\n  json: { countryCode, country: $json['Country'] },\n  binary: firstKey ? { data: binary[firstKey] } : undefined\n};"
      },
      "id": "bf4c640f-e428-4318-99b8-e64e73587faf",
      "name": "Extract Country Code",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3888,
        1808
      ]
    },
    {
      "parameters": {
        "operation": "parseAutoDetect",
        "options": {}
      },
      "id": "7d9f212d-42a9-48a6-af8c-84b4ac163edb",
      "name": "Parse Invoice",
      "type": "n8n-nodes-invoice-api-xhub.invoiceXhub",
      "typeVersion": 1,
      "position": [
        4128,
        1808
      ],
      "credentials": {
        "invoiceXhubApi": {
          "id": "",
          "name": "Invoice-api.xhub Account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-parse-form",
              "leftValue": "={{ $json.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8d19956e-ca4f-436e-9ecc-93c2bbc1b63d",
      "name": "Parse OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        4368,
        1808
      ]
    },
    {
      "parameters": {
        "operation": "validate",
        "countryCode": {
          "__rl": true,
          "value": "={{ $('Extract Country Code').item.json.countryCode }}",
          "mode": "expression"
        },
        "invoiceData": "={{ JSON.stringify($json.invoice) }}",
        "options": {}
      },
      "id": "2e697578-7336-429e-be90-2d91393e2726",
      "name": "Validate Invoice",
      "type": "n8n-nodes-invoice-api-xhub.invoiceXhub",
      "typeVersion": 1,
      "position": [
        4608,
        1712
      ],
      "credentials": {
        "invoiceXhubApi": {
          "id": "",
          "name": "Invoice-api.xhub Account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const validation = $json;\nconst parseResult = $('Parse Invoice').item.json;\nconst inv = parseResult.invoice || {};\nconst seller = inv.seller || {};\nconst buyer = inv.buyer || {};\nconst bank = seller.bankAccount || {};\nconst items = Array.isArray(inv.items) ? inv.items : [];\nconst pt = inv.paymentTerms || {};\nconst cs = inv.countrySpecific || {};\nconst isValid = validation.valid === true;\nconst errors = validation.errors || [];\nconst warnings = validation.warnings || parseResult.warnings || [];\nconst sellerBank = bank.iban ? `${bank.iban}${bank.bic ? ' / ' + bank.bic : ''}${bank.bankName ? ' (' + bank.bankName + ')' : ''}` : '';\nconst lineItemsSummary = items.map(i => `${i.quantity || ''}x ${i.description || ''} @ ${i.unitPrice || ''}`).join(' | ').slice(0, 500);\nconst paymentTermsText = [pt.description, pt.dueDays ? `${pt.dueDays} Tage` : ''].filter(Boolean).join(' — ');\nconst totalVat = Array.isArray(inv.taxSummary) ? inv.taxSummary.reduce((sum, t) => sum + (Number(t.taxAmount) || 0), 0) : '';\nreturn { json: {\n  status: isValid ? 'VALID' : 'INVALID',\n  format: parseResult.format || parseResult.detectedFormat || 'unknown',\n  country: $('Extract Country Code').item.json.countryCode,\n  confidence: parseResult.confidence != null ? parseResult.confidence : '',\n  invoiceNumber: inv.invoiceNumber || '',\n  invoiceType: inv.type || '',\n  issueDate: inv.issueDate || '',\n  dueDate: inv.dueDate || '',\n  deliveryDate: inv.deliveryDate || inv.delivery?.date || '',\n  currency: inv.currency || 'EUR',\n  subtotal: inv.subtotal != null ? inv.subtotal : '',\n  totalVat,\n  total: inv.total != null ? inv.total : '',\n  sellerName: seller.name || '',\n  sellerVatId: seller.vatId || seller.taxId || '',\n  sellerStreet: seller.street || '',\n  sellerCity: seller.city || '',\n  sellerPostalCode: seller.postalCode || '',\n  sellerCountry: seller.countryCode || '',\n  sellerEmail: seller.email || '',\n  sellerPhone: seller.phone || '',\n  sellerBank,\n  buyerName: buyer.name || '',\n  buyerVatId: buyer.vatId || buyer.taxId || '',\n  buyerStreet: buyer.street || '',\n  buyerCity: buyer.city || '',\n  buyerPostalCode: buyer.postalCode || '',\n  buyerCountry: buyer.countryCode || '',\n  buyerEmail: buyer.email || '',\n  buyerReference: cs.buyerReference || cs.leitwegId || '',\n  leitwegId: cs.leitwegId || '',\n  orderNumber: inv.orderNumber || '',\n  customerNumber: inv.customerNumber || '',\n  contractNumber: inv.contractNumber || '',\n  lineItemsCount: items.length,\n  lineItemsSummary,\n  paymentTerms: paymentTermsText,\n  notes: inv.notes || '',\n  documentHash: parseResult.hash || '',\n  errorCount: errors.length,\n  warningCount: warnings.length,\n  errors: errors.map(e => typeof e === 'string' ? e : (e.message || JSON.stringify(e))).join(' | '),\n  warnings: warnings.map(w => typeof w === 'string' ? w : (w.message || JSON.stringify(w))).join(' | ')\n} };"
      },
      "id": "0feff150-e65e-48d5-9aea-b90429c943c6",
      "name": "Format Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4848,
        1712
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "return {\n  json: {\n    status: 'PARSE ERROR',\n    format: 'unknown',\n    country: $('Extract Country Code').item.json.countryCode,\n    invoiceNumber: 'N/A',\n    sellerName: 'N/A',\n    buyerName: 'N/A',\n    total: 'N/A',\n    errorCount: 1,\n    warningCount: 0,\n    errors: $('Parse Invoice').item.json.error || 'Could not parse the uploaded file. Make sure it is a valid e-invoice (XRechnung, ZUGFeRD, Factur-X, UBL).',\n    warnings: ''\n  }\n};"
      },
      "id": "2ab1160f-0318-47c8-af68-1eeb06f09ddc",
      "name": "Format Parse Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4608,
        1936
      ]
    },
    {
      "parameters": {
        "content": "## Invoice validator via n8n form\n\nUpload a file, pick a country, get back `VALID` / `INVALID` / `PARSE ERROR` with the parsed fields. Uses invoice-api.xhub for both parse (auto-detect) and validate.\n\n**Setup:** install the `n8n-nodes-invoice-api-xhub` community node, add the credential (free sandbox keys available), activate — the Upload Form node prints the URL.\n\n**Extend:** swap the final response for a branch that routes `VALID` results to a database and `INVALID` ones into a Slack or ticketing flow.",
        "height": 320,
        "width": 1340,
        "color": 1
      },
      "id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3616,
        960
      ]
    },
    {
      "parameters": {
        "content": "💡 **TIP:** Share this form URL with your team for self-service invoice validation",
        "height": 80,
        "width": 340
      },
      "id": "c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3616,
        1632
      ]
    },
    {
      "parameters": {
        "content": "## 1. Upload & Auto-Detect\nUser uploads a file; format is auto-detected and parsed.",
        "height": 248,
        "width": 926
      },
      "id": "82cebd09-b7fc-4480-88fa-9dfc8617f3db",
      "name": "Section: Upload & Parse",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3600,
        1728
      ]
    },
    {
      "parameters": {
        "content": "## 2. Validate & Respond\nRun EN 16931 validation and return VALID / INVALID / PARSE ERROR.",
        "height": 432,
        "width": 496
      },
      "id": "0db07642-61bf-4a4b-b683-673f1b5691e5",
      "name": "Section: Validate",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        4560,
        1632
      ]
    }
  ],
  "connections": {
    "Upload Form": {
      "main": [
        [
          {
            "node": "Extract Country Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Country Code": {
      "main": [
        [
          {
            "node": "Parse Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Invoice": {
      "main": [
        [
          {
            "node": "Parse OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse OK?": {
      "main": [
        [
          {
            "node": "Validate Invoice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Parse Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Invoice": {
      "main": [
        [
          {
            "node": "Format Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}