{
  "name": "ScanKit: Delivery note scan to approval",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "scankit-delivery-approval",
        "responseMode": "onReceived",
        "responseData": "noData",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        0
      ],
      "id": "d6214f3c-3277-4f5a-bfb7-a64f988487b5",
      "name": "ScanKit Delivery Note Webhook",
      "webhookId": "e18bbde8-2049-434d-8e15-c80a08e5bdc7"
    },
    {
      "parameters": {
        "jsCode": "// Normalize the ScanKit webhook envelope (delivery notes).\n// ScanKit sends ONLY the cleaned scan:\n//   multipart: file (scan.pdf) + metadata JSON\n//     { scanner_id, scanner_name, page_count, timestamp }\n// No OCR text, no confidence score. Extraction is a downstream step.\nconst item = { json: {}, binary: {} };\nlet meta = {};\nif (typeof $json.metadata === 'string') {\n  try { meta = JSON.parse($json.metadata); } catch (e) { meta = {}; }\n} else if ($json.metadata && typeof $json.metadata === 'object') {\n  meta = $json.metadata;\n} else {\n  meta = $json;\n}\n// Fictional example values are used when the envelope is empty.\nitem.json = {\n  scanner_id: meta.scanner_id || 7,                          // fictional\n  scanner_name: meta.scanner_name || 'Delivery Notes',       // fictional\n  page_count: meta.page_count || 1,\n  timestamp: meta.timestamp || new Date().toISOString(),\n};\nif ($binary && Object.keys($binary).length) {\n  item.binary.scan = $binary[Object.keys($binary)[0]];\n}\nreturn [item];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "id": "66ef67c0-abbe-4e11-abe7-0ff86bed2629",
      "name": "Normalize ScanKit payload"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://YOUR-VISION-LLM-ENDPOINT.example/v1/chat/completions",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: \"your-vision-model\", messages: [{ role: \"user\", content: \"Extract delivery_number, supplier, line_items, total from this scan. Return JSON only.\" }] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ],
      "id": "ae1af8f1-7cb8-4755-9291-d9df241b8450",
      "name": "Vision LLM: extract delivery fields"
    },
    {
      "parameters": {
        "jsCode": "// Parse the LLM response. Branch B is taken when a mandatory field is\n// missing or the total is not plausible. The workflow decides this, not\n// ScanKit (ScanKit sends no confidence score).\nlet out = { json: { ok: false, fields: {}, reason: 'no response' }, binary: {} };\nif ($binary && $binary.data) { out.binary.scan = $binary.data; }\ntry {\n  let text = '';\n  const body = $json;\n  if (typeof body === 'string') text = body;\n  else if (body && body.choices && body.choices[0]) text = body.choices[0].message && body.choices[0].message.content || JSON.stringify(body);\n  else if (body && body.message && body.message.content) text = body.message.content;\n  else text = JSON.stringify(body);\n  let parsed = text;\n  try { parsed = JSON.parse(text); } catch (e) {}\n  if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch (e2) {} }\n  const f = parsed.fields || parsed.parsed || (parsed.delivery_number ? parsed : {});\n  const total = Number(f.total);\n  const hasLines = Array.isArray(f.line_items) && f.line_items.length > 0;\n  const ok = !!(f.delivery_number && f.supplier && hasLines && total > 0 && total < 1000000);\n  out.json = { ok, fields: f, total, reason: ok ? 'complete' : 'mandatory field missing or total not plausible' };\n} catch (e) {\n  out.json = { ok: false, fields: {}, reason: String(e && e.message || e) };\n}\nreturn [out];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "id": "cff96a11-cf8c-4020-b7e5-d7a9eec55966",
      "name": "Parse and flag missing fields"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "70e5663d-8210-4989-aaf5-c798a5b0538f",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        0
      ],
      "id": "fbac3824-c3dc-4a49-8f49-544b7fd1a6d7",
      "name": "Complete and plausible?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://YOUR-ERP-ENDPOINT.example/delivery-notes",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.fields) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        -160
      ],
      "id": "0962994b-cbdd-4c96-8110-97fc2e91f3c8",
      "name": "Update ERP directly"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://YOUR-APPROVAL-CHANNEL.example/approve",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ scanner_name: $json.scanner_name || null, reason: $json.reason || \"incomplete\", fields: $json.fields || {}, doc: \"link-to-scan-pdf\" }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        160
      ],
      "id": "a4416c3e-ffd7-486d-85c4-aacf0ade59a5",
      "name": "Send approval request"
    },
    {
      "parameters": {
        "content": "Workflow template: delivery note scan with approval.\n\n1. Hosted ScanKit scanner points at THIS workflow webhook URL.\n2. Webhook receives the cleaned scan only (file + metadata JSON). No OCR or confidence from ScanKit.\n3. Replace the Vision LLM endpoint (YOUR-VISION-LLM-ENDPOINT) with your vision-capable model.\n4. Send approval request to your email/Slack integration (YOUR-APPROVAL-CHANNEL).\n5. Both branches write to your ERP or tracker (YOUR-ERP-ENDPOINT).\n6. Test with a clean and a crumpled note to see both branches.\n\nAll example values are fictional.",
        "height": 400,
        "width": 300
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        340
      ],
      "id": "a07df9a3-bd9c-47d6-9678-85c14889c2ac",
      "name": "How to use"
    }
  ],
  "pinData": {},
  "connections": {
    "ScanKit Delivery Note Webhook": {
      "main": [
        [
          {
            "node": "Normalize ScanKit payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize ScanKit payload": {
      "main": [
        [
          {
            "node": "Vision LLM: extract delivery fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Vision LLM: extract delivery fields": {
      "main": [
        [
          {
            "node": "Parse and flag missing fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and flag missing fields": {
      "main": [
        [
          {
            "node": "Complete and plausible?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Complete and plausible?": {
      "main": [
        [
          {
            "node": "Update ERP directly",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send approval request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a1db9870-7af8-4835-a461-ae0e68819bfc",
  "meta": {},
  "id": "321b32e2-d1e0-4a47-9d0e-ef52bb948a8a",
  "tags": []
}