AutomationFlowsAI & RAG › Document Intake with AI Classification

Document Intake with AI Classification

Document Intake with AI Classification. Uses dataTable, openAi, gmail. Webhook trigger; 34 nodes.

Webhook trigger★★★★★ complexityAI-powered34 nodesData TableOpenAIGmail
AI & RAG Trigger: Webhook Nodes: 34 Complexity: ★★★★★ AI nodes: yes Added:

This workflow follows the Datatable → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Document Intake with AI Classification",
  "activeVersionId": null,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "connections": {
    "Document Intake Webhook": {
      "main": [
        [
          {
            "node": "Validate Intake Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Intake Token": {
      "main": [
        [
          {
            "node": "Intake Authorized?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Intake Authorized?": {
      "main": [
        [
          {
            "node": "Normalize Document Intake",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Intake Unauthorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Document Intake": {
      "main": [
        [
          {
            "node": "Claim Document Key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claim Document Key": {
      "main": [
        [
          {
            "node": "Document Claim Duplicate?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Document Claim Duplicate?": {
      "main": [
        [
          {
            "node": "Find Claimed Document Row",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Find Existing Document Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Claimed Document Row": {
      "main": [
        [
          {
            "node": "Respond Document Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Existing Document Row": {
      "main": [
        [
          {
            "node": "Persistent Document Duplicate?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Persistent Document Duplicate?": {
      "main": [
        [
          {
            "node": "Respond Document Duplicate",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Document Intake Dead Letter?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Document Intake Dead Letter?": {
      "main": [
        [
          {
            "node": "Insert Document Dead Letter",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OCR Usable?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Document Dead Letter": {
      "main": [
        [
          {
            "node": "Mark Document Claim Dead Letter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Document Claim Dead Letter": {
      "main": [
        [
          {
            "node": "Respond Document Dead Letter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OCR Usable?": {
      "main": [
        [
          {
            "node": "Extract Document Fields",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert OCR Needs Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Document Fields": {
      "main": [
        [
          {
            "node": "Validate AI Extraction",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate AI Extraction": {
      "main": [
        [
          {
            "node": "AI Validated Auto File?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Validated Auto File?": {
      "main": [
        [
          {
            "node": "Insert Filed Document Record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert AI Needs Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Filed Document Record": {
      "main": [
        [
          {
            "node": "Mark Document Claim Filed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Document Claim Filed": {
      "main": [
        [
          {
            "node": "Respond Document Filed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert AI Needs Review": {
      "main": [
        [
          {
            "node": "Send Controlled AI Review Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Controlled AI Review Alert": {
      "main": [
        [
          {
            "node": "Build AI Review Alert Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build AI Review Alert Update": {
      "main": [
        [
          {
            "node": "Update AI Review Alert Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update AI Review Alert Sent": {
      "main": [
        [
          {
            "node": "Mark Document Claim AI Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Document Claim AI Review": {
      "main": [
        [
          {
            "node": "Respond AI Needs Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert OCR Needs Review": {
      "main": [
        [
          {
            "node": "Send Controlled OCR Review Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Controlled OCR Review Alert": {
      "main": [
        [
          {
            "node": "Build OCR Review Alert Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build OCR Review Alert Update": {
      "main": [
        [
          {
            "node": "Update OCR Review Alert Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update OCR Review Alert Sent": {
      "main": [
        [
          {
            "node": "Mark Document Claim OCR Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Document Claim OCR Review": {
      "main": [
        [
          {
            "node": "Respond OCR Needs Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "nodes": [
    {
      "id": "5e2ad64c-6e48-45bb-9557-a5c06f386a65",
      "name": "Document Intake Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        260
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "document-intake-test",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "b7a08f56-e876-4a13-8023-d0caa5286154",
      "name": "Validate Intake Token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        260
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const root = $input.item.json || {};\nconst headers = root.headers || {};\nconst header = (name) => headers[name] || headers[name.toLowerCase()] || headers[name.toUpperCase()] || '';\nconst rawAuth = String(header('authorization') || '');\nconst bearer = rawAuth.toLowerCase().startsWith('bearer ') ? rawAuth.slice(7).trim() : '';\nconst provided = String(header('x-doc-intake-token') || bearer || '').trim();\nconst expected = String($vars.DOC_INTAKE_WEBHOOK_TOKEN || '').trim();\nreturn { json: {\n  ...root,\n  auth_ok: Boolean(expected && provided && provided === expected),\n  auth_reason: !expected ? 'missing_server_token_var' : !provided ? 'missing_token_header' : provided === expected ? '' : 'bad_token',\n  checked_at: new Date().toISOString(),\n} };"
      }
    },
    {
      "id": "ca7a8169-1bd3-4115-8d76-3b05f12dbd9c",
      "name": "Intake Authorized?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        520,
        260
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.auth_ok }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "013d1a1c-c9bc-4d57-bf54-dea04579eb91",
      "name": "Normalize Document Intake",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        260
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const crypto = require('crypto');\nconst root = $input.item.json || {};\nconst body = root.body && typeof root.body === 'object' ? root.body : root;\nconst now = new Date().toISOString();\nconst collapse = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();\nconst truncate = (value, max) => collapse(value).slice(0, max);\nconst htmlEscape = (value) => String(value ?? '').replace(/[&<>\"']/g, ch => ({\n  '&': '&amp;',\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;',\n  \"'\": '&#39;',\n}[ch]));\nconst filename = truncate(body.filename || body.file_name || 'unnamed-document', 180);\nconst mime_type = truncate(body.mime_type || body.mimeType || '', 120);\nconst attachment_id = truncate(body.attachment_id || body.attachmentId || '', 180);\nconst file_sha256_raw = String(body.file_sha256 || body.sha256 || '').toLowerCase();\nconst file_sha256_valid = /^[a-f0-9]{64}$/.test(file_sha256_raw);\nconst file_sha256 = file_sha256_valid ? file_sha256_raw : '';\nconst file_sha256_malformed = Boolean(file_sha256_raw && !file_sha256_valid);\nconst rawText = String(body.ocr_text || body.text || body.extracted_text || '');\nconst ocr_confidence = Number(body.ocr_confidence ?? body.ocrConfidence ?? 0);\nconst scan_text_ratio = Number(body.scan_text_ratio ?? body.scanTextRatio ?? 0);\nconst supportedMime = ['application/pdf', 'image/png', 'image/jpeg', 'image/tiff', 'image/heic', 'text/plain'].includes(mime_type);\nconst errors = [];\nif (!filename || filename === 'unnamed-document') errors.push('filename missing');\nif (!supportedMime) errors.push('unsupported mime_type');\nif (!attachment_id && !file_sha256) errors.push('attachment_id or file_sha256 required');\nif (file_sha256_malformed) errors.push('file_sha256 must be 64 hex characters when provided');\nif (!rawText.trim()) errors.push('ocr_text missing');\nconst textChars = rawText.replace(/\\s/g, '').length;\nconst replacementRatio = textChars ? ((rawText.match(/[\ufffd]/g) || []).length / textChars) : 1;\nconst alphaRatio = textChars ? ((rawText.match(/[\\p{L}\\p{N}]/gu) || []).length / textChars) : 0;\nconst ocr_usable = rawText.trim().length >= 80 && ocr_confidence >= 0.55 && scan_text_ratio >= 0.15 && replacementRatio < 0.08 && alphaRatio >= 0.45;\nconst document_key_seed = file_sha256 || (attachment_id ? [attachment_id, filename, mime_type].join('|') : [filename, mime_type, rawText.slice(0, 2000)].join('|'));\nconst document_key = crypto.createHash('sha256').update('doc-intake\\n' + document_key_seed).digest('hex');\nconst redactions = [\n  { re: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi, token: '[email-redacted]' },\n  { re: /\\b[A-Za-z]{2}\\d{2}(?:[ -]?[A-Za-z0-9]){11,30}\\b/g, token: '[iban-redacted]' },\n  { re: /\\b(?:NIP[:\\s-]*)?\\d{3}[ -]?\\d{3}[ -]?\\d{2}[ -]?\\d{2}\\b/gi, token: '[tax-id-redacted]' },\n  { re: /\\b\\d{11}\\b/g, token: '[national-id-redacted]' },\n  { re: /\\b(?:\\+?\\d[\\d\\s().-]{7,}\\d)\\b/g, token: '[phone-redacted]' },\n];\nconst redactForModel = (value) => {\n  let text = String(value ?? '');\n  let changed = false;\n  for (const rule of redactions) {\n    const next = text.replace(rule.re, rule.token);\n    if (next !== text) changed = true;\n    text = next;\n  }\n  return { text, changed };\n};\nconst redactedBody = redactForModel(rawText);\nconst redactedFilename = redactForModel(filename);\nlet modelText = redactedBody.text;\nlet filenameForModel = redactedFilename.text;\nlet pii_redacted_for_model = redactedBody.changed || redactedFilename.changed;\nmodelText = modelText.replace(/\\s+/g, ' ').trim().slice(0, 6000);\nfilenameForModel = filenameForModel.replace(/\\s+/g, ' ').trim().slice(0, 180);\nconst invalid_input = errors.length > 0 || !ocr_usable;\nlet status = 'AI Pending';\nlet review_reason = '';\nlet last_error_class = '';\nlet last_error_message = '';\nif (errors.length > 0) {\n  status = supportedMime ? 'Needs Review' : 'Dead Letter';\n  review_reason = errors.join('; ');\n  last_error_class = supportedMime ? 'business' : 'permanent';\n  last_error_message = review_reason;\n} else if (!ocr_usable) {\n  status = 'Needs Review';\n  review_reason = 'ocr_unusable: low confidence, too little text, or OCR garbage';\n  last_error_class = 'business';\n  last_error_message = review_reason;\n}\nreturn { json: {\n  document_key,\n  attachment_id,\n  filename,\n  filename_html: htmlEscape(filename),\n  mime_type,\n  file_sha256,\n  status,\n  document_type: '',\n  party_name: '',\n  party_name_html: '',\n  document_date: '',\n  amount_total: null,\n  currency: 'Unknown',\n  confidence: 0,\n  validation_errors: errors.join('; '),\n  review_reason,\n  review_reason_html: htmlEscape(review_reason),\n  archive_path: '',\n  ocr_confidence: Number.isFinite(ocr_confidence) ? ocr_confidence : 0,\n  scan_text_ratio: Number.isFinite(scan_text_ratio) ? scan_text_ratio : 0,\n  model_transport_chars: modelText.length,\n  pii_redacted_for_model,\n  review_alert_sent: false,\n  review_alert_id: '',\n  ai_raw_json: '',\n  model_prompt_excerpt: modelText.slice(0, 1200),\n  ai_prompt_text: modelText,\n  filename_for_model: filenameForModel,\n  ocr_usable,\n  invalid_input,\n  raw_event_json: JSON.stringify({ source: body.source || '', attachment_id, filename, mime_type, file_sha256, ocr_confidence, scan_text_ratio, omitted_fields: ['sender_email', 'email_body', 'raw_ocr_text'] }).slice(0, 9000),\n  last_execution_id: $execution.id,\n  last_error_class,\n  last_error_message,\n  created_at: now,\n  updated_at: now,\n} };"
      }
    },
    {
      "id": "0c9f3b4b-4269-43ba-bb4b-73151148b5eb",
      "name": "Claim Document Key",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        260
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const pruneStore = (obj, max = 5000) => {\n  const keys = Object.keys(obj);\n  for (let i = 0; i < keys.length - max; i++) delete obj[keys[i]];\n};\nconst n = $input.item.json;\nconst store = $getWorkflowStaticData('global');\nstore.document_claims = store.document_claims || {};\npruneStore(store.document_claims);\nconst claims = store.document_claims;\nconst now = Date.now();\nconst claim = claims[n.document_key];\nconst claimAge = claim?.claimed_at ? now - Date.parse(claim.claimed_at) : 0;\nconst activeClaim = claim && (claim.status !== 'in_progress' || claimAge < 15 * 60 * 1000);\nif (activeClaim) {\n  return { json: { ...n, duplicate_claim: true, duplicate_status: claim.status || 'seen', existing_record_id: claim.record_id || '' } };\n}\nclaims[n.document_key] = { status: 'in_progress', claimed_at: new Date(now).toISOString(), first_execution_id: n.last_execution_id };\nreturn { json: { ...n, duplicate_claim: false, duplicate_status: '', existing_record_id: '' } };"
      }
    },
    {
      "id": "27f34597-bb07-4403-a558-fa8b272c505a",
      "name": "Document Claim Duplicate?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1300,
        260
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.duplicate_claim }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "0057a990-14db-4588-8a3e-a125500e27be",
      "name": "Find Claimed Document Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1560,
        80
      ],
      "parameters": {
        "resource": "row",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_TABLE_ID",
          "cachedResultName": "Intake_Documents"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "document_key",
              "condition": "eq",
              "keyValue": "={{ $(\"Normalize Document Intake\").item.json.document_key }}"
            }
          ]
        },
        "returnAll": false,
        "limit": 1
      },
      "alwaysOutputData": true
    },
    {
      "id": "34f095af-72c2-42bc-8016-a5e3be8fa866",
      "name": "Respond Document Duplicate",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        2080,
        80
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, status: \"duplicate\", document_key: $(\"Normalize Document Intake\").item.json.document_key, existing_record_id: $json.id || $(\"Claim Document Key\").item.json.existing_record_id || \"\", existing_status: $json.status || $(\"Claim Document Key\").item.json.duplicate_status || \"seen\" } }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "4e134505-da38-41fd-95d5-8e9878726724",
      "name": "Find Existing Document Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1560,
        320
      ],
      "parameters": {
        "resource": "row",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_TABLE_ID",
          "cachedResultName": "Intake_Documents"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "document_key",
              "condition": "eq",
              "keyValue": "={{ $(\"Normalize Document Intake\").item.json.document_key }}"
            }
          ]
        },
        "returnAll": false,
        "limit": 1
      },
      "alwaysOutputData": true
    },
    {
      "id": "5b9e4d5a-9f78-4632-953b-21ce85876aa9",
      "name": "Persistent Document Duplicate?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1820,
        320
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ !!$json.id }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "74a1ec43-4ea3-4bb4-880a-301215565f63",
      "name": "Document Intake Dead Letter?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        2080,
        320
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $(\"Normalize Document Intake\").item.json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "Dead Letter"
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "1f7d29b1-1cda-482c-8a5e-77db246dee66",
      "name": "Insert Document Dead Letter",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        2340,
        160
      ],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_TABLE_ID",
          "cachedResultName": "Intake_Documents"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "document_key": "={{ $(\"Normalize Document Intake\").item.json.document_key }}",
            "attachment_id": "={{ $(\"Normalize Document Intake\").item.json.attachment_id }}",
            "filename": "={{ $(\"Normalize Document Intake\").item.json.filename }}",
            "mime_type": "={{ $(\"Normalize Document Intake\").item.json.mime_type }}",
            "file_sha256": "={{ $(\"Normalize Document Intake\").item.json.file_sha256 }}",
            "status": "={{ $(\"Normalize Document Intake\").item.json.status }}",
            "document_type": "={{ $(\"Normalize Document Intake\").item.json.document_type }}",
            "party_name": "={{ $(\"Normalize Document Intake\").item.json.party_name }}",
            "document_date": "={{ $(\"Normalize Document Intake\").item.json.document_date }}",
            "amount_total": "={{ $(\"Normalize Document Intake\").item.json.amount_total }}",
            "currency": "={{ $(\"Normalize Document Intake\").item.json.currency }}",
            "confidence": "={{ $(\"Normalize Document Intake\").item.json.confidence }}",
            "validation_errors": "={{ $(\"Normalize Document Intake\").item.json.validation_errors }}",
            "review_reason": "={{ $(\"Normalize Document Intake\").item.json.review_reason }}",
            "archive_path": "={{ $(\"Normalize Document Intake\").item.json.archive_path }}",
            "ocr_confidence": "={{ $(\"Normalize Document Intake\").item.json.ocr_confidence }}",
            "model_transport_chars": "={{ $(\"Normalize Document Intake\").item.json.model_transport_chars }}",
            "pii_redacted_for_model": "={{ $(\"Normalize Document Intake\").item.json.pii_redacted_for_model }}",
            "review_alert_sent": "={{ $(\"Normalize Document Intake\").item.json.review_alert_sent }}",
            "review_alert_id": "={{ $(\"Normalize Document Intake\").item.json.review_alert_id }}",
            "ai_raw_json": "={{ $(\"Normalize Document Intake\").item.json.ai_raw_json }}",
            "model_prompt_excerpt": "={{ $(\"Normalize Document Intake\").item.json.model_prompt_excerpt }}",
            "raw_event_json": "={{ $(\"Normalize Document Intake\").item.json.raw_event_json }}",
            "last_execution_id": "={{ $(\"Normalize Document Intake\").item.json.last_execution_id }}",
            "last_error_class": "={{ $(\"Normalize Document Intake\").item.json.last_error_class }}",
            "last_error_message": "={{ $(\"Normalize Document Intake\").item.json.last_error_message }}",
            "created_at": "={{ $(\"Normalize Document Intake\").item.json.created_at }}",
            "updated_at": "={{ $(\"Normalize Document Intake\").item.json.updated_at }}"
          },
          "schema": [
            {
              "id": "document_key",
              "displayName": "document_key",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "attachment_id",
              "displayName": "attachment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "filename",
              "displayName": "filename",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "mime_type",
              "displayName": "mime_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "file_sha256",
              "displayName": "file_sha256",
              "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": "document_type",
              "displayName": "document_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "party_name",
              "displayName": "party_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "document_date",
              "displayName": "document_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "amount_total",
              "displayName": "amount_total",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "currency",
              "displayName": "currency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "confidence",
              "displayName": "confidence",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "validation_errors",
              "displayName": "validation_errors",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_reason",
              "displayName": "review_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "archive_path",
              "displayName": "archive_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "ocr_confidence",
              "displayName": "ocr_confidence",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "model_transport_chars",
              "displayName": "model_transport_chars",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "pii_redacted_for_model",
              "displayName": "pii_redacted_for_model",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_alert_sent",
              "displayName": "review_alert_sent",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_alert_id",
              "displayName": "review_alert_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "ai_raw_json",
              "displayName": "ai_raw_json",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "model_prompt_excerpt",
              "displayName": "model_prompt_excerpt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "raw_event_json",
              "displayName": "raw_event_json",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_execution_id",
              "displayName": "last_execution_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_error_class",
              "displayName": "last_error_class",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_error_message",
              "displayName": "last_error_message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": false
            },
            {
              "id": "updated_at",
              "displayName": "updated_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": false
            }
          ]
        }
      }
    },
    {
      "id": "2bd12599-932f-4f94-9739-e08777003d59",
      "name": "Mark Document Claim Dead Letter",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2600,
        160
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const pruneStore = (obj, max = 5000) => {\n  const keys = Object.keys(obj);\n  for (let i = 0; i < keys.length - max; i++) delete obj[keys[i]];\n};\nconst n = $('Normalize Document Intake').item.json;\nconst record = $input.item.json || {};\nconst store = $getWorkflowStaticData('global');\nstore.document_claims = store.document_claims || {};\npruneStore(store.document_claims);\nstore.document_claims[n.document_key] = { ...(store.document_claims[n.document_key] || {}), status: 'Dead Letter', record_id: record.id || '', completed_at: new Date().toISOString() };\nreturn { json: record };"
      }
    },
    {
      "id": "f05da482-2fc1-4620-8e08-e224dee9c708",
      "name": "Respond Document Dead Letter",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        2860,
        160
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, status: \"dead_letter\", document_key: $(\"Normalize Document Intake\").item.json.document_key, record_id: $json.id || \"\", reason: $(\"Normalize Document Intake\").item.json.review_reason || $(\"Normalize Document Intake\").item.json.validation_errors } }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "1eaa6cd5-9324-4449-ba02-60ed3bbceccc",
      "name": "OCR Usable?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        2340,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $(\"Normalize Document Intake\").item.json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "AI Pending"
            },
            {
              "leftValue": "={{ $(\"Normalize Document Intake\").item.json.ocr_usable }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "b8c999fe-d452-4a05-86e4-73c36d335c05",
      "name": "Extract Document Fields",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.3,
      "position": [
        2600,
        340
      ],
      "parameters": {
        "resource": "text",
        "operation": "response",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "responses": {
          "values": [
            {
              "type": "text",
              "role": "system",
              "content": "You extract document metadata for a law/accounting office. Return only JSON text, not markdown. Closed document_type enum: Invoice, Contract, Receipt, Other. Closed currency enum: PLN, EUR, USD, GBP, Unknown. Extract only: document_type, party_name, document_date as YYYY-MM-DD or null, amount_total as number or null, currency, confidence 0..1, field_confidence object with document_type, party_name, document_date, amount_total 0..1, needs_human_review boolean, review_reason string, evidence object with short text snippets. If the text is OCR garbage or a type does not fit the enum, use Other and needs_human_review=true. Never invent missing fields."
            },
            {
              "type": "text",
              "role": "user",
              "content": "=Filename: {{ $(\"Normalize Document Intake\").item.json.filename_for_model }}\nMIME: {{ $(\"Normalize Document Intake\").item.json.mime_type }}\nOCR confidence: {{ $(\"Normalize Document Intake\").item.json.ocr_confidence }}\nRedacted OCR excerpt for extraction only:\n\"\"\"{{ $(\"Normalize Document Intake\").item.json.ai_prompt_text }}\"\"\""
            }
          ]
        },
        "simplify": true,
        "options": {
          "temperature": 0,
          "store": false
        }
      }
    },
    {
      "id": "a075a974-ea0d-4293-abac-0ad1336678c6",
      "name": "Validate AI Extraction",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        340
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const base = $('Normalize Document Intake').item.json;\nconst raw = $input.item.json;\nconst extractText = (value) => {\n  if (typeof value === 'string') return value;\n  if (Array.isArray(value)) return value.map(extractText).filter(Boolean).join('\\n');\n  if (value && typeof value === 'object') {\n    if (typeof value.text === 'string') return value.text;\n    if (typeof value.output_text === 'string') return value.output_text;\n    if (value.content) return extractText(value.content);\n    if (value.output) return extractText(value.output);\n  }\n  return '';\n};\nlet ai = raw.output ?? raw.text ?? raw.content ?? raw;\nlet parsedFrom = 'object';\nif (typeof ai === 'string') {\n  parsedFrom = 'string';\n  const cleaned = ai.replace(/^\\s*```(?:json)?\\s*/i, '').replace(/\\s*```\\s*$/i, '').trim();\n  try { ai = JSON.parse(cleaned); } catch { ai = null; }\n} else {\n  const extracted = extractText(ai);\n  if (extracted) {\n    const cleaned = extracted.replace(/^\\s*```(?:json)?\\s*/i, '').replace(/\\s*```\\s*$/i, '').trim();\n    try { ai = JSON.parse(cleaned); parsedFrom = 'nested_text'; } catch { /* keep object */ }\n  }\n}\nconst allowedTypes = ['Invoice', 'Contract', 'Receipt', 'Other'];\nconst allowedCurrency = ['PLN', 'EUR', 'USD', 'GBP', 'Unknown'];\nconst reasons = [];\nconst warnings = [];\nconst htmlEscape = (value) => String(value ?? '').replace(/[&<>\"']/g, ch => ({\n  '&': '&amp;',\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;',\n  \"'\": '&#39;',\n}[ch]));\nif (!ai || typeof ai !== 'object' || Array.isArray(ai)) reasons.push('AI output unparseable or not an object');\nai = ai && typeof ai === 'object' && !Array.isArray(ai) ? ai : {};\nconst enumPick = (value, allowed, fallback, label) => {\n  const v = typeof value === 'string' ? value.trim() : '';\n  if (allowed.includes(v)) return v;\n  reasons.push(label + ' outside closed enum');\n  return fallback;\n};\nconst clamp01 = (value, label) => {\n  let n = Number(value);\n  if (!Number.isFinite(n)) {\n    reasons.push(label + ' missing or nonnumeric');\n    n = 0;\n  }\n  return Math.max(0, Math.min(1, n));\n};\nconst document_type = enumPick(ai.document_type, allowedTypes, 'Other', 'document_type');\nconst currency = enumPick(ai.currency || 'Unknown', allowedCurrency, 'Unknown', 'currency');\nconst confidence = clamp01(ai.confidence, 'confidence');\nconst fc = ai.field_confidence && typeof ai.field_confidence === 'object' ? ai.field_confidence : {};\nconst typeConfidence = clamp01(fc.document_type ?? confidence, 'field_confidence.document_type');\nconst partyConfidence = clamp01(fc.party_name ?? confidence, 'field_confidence.party_name');\nconst dateConfidence = clamp01(fc.document_date ?? confidence, 'field_confidence.document_date');\nconst amountConfidence = clamp01(fc.amount_total ?? confidence, 'field_confidence.amount_total');\nconst party_name = typeof ai.party_name === 'string' ? ai.party_name.replace(/\\s+/g, ' ').trim().slice(0, 140) : '';\nif (party_name.length < 2) reasons.push('party_name missing or too short');\nlet document_date = typeof ai.document_date === 'string' ? ai.document_date.trim() : '';\nif (document_date && !/^\\d{4}-\\d{2}-\\d{2}$/.test(document_date)) {\n  reasons.push('document_date not YYYY-MM-DD');\n  document_date = '';\n}\nif (document_date) {\n  const d = new Date(document_date + 'T00:00:00Z');\n  if (!Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) !== document_date) {\n    reasons.push('document_date not a real calendar date');\n    document_date = '';\n  }\n  const min = new Date('1990-01-01T00:00:00Z');\n  const max = new Date(Date.now() + 366 * 24 * 60 * 60 * 1000);\n  if (Number.isNaN(d.getTime()) || d < min || d > max) {\n    reasons.push('document_date outside sane range');\n    document_date = '';\n  }\n}\nif (!document_date) reasons.push('document_date missing');\nlet amount_total = ai.amount_total === null || ai.amount_total === undefined || ai.amount_total === '' ? null : Number(ai.amount_total);\nif (amount_total !== null && (!Number.isFinite(amount_total) || amount_total < 0 || amount_total > 10000000)) {\n  reasons.push('amount_total outside sane range');\n  amount_total = null;\n}\nif ((document_type === 'Invoice' || document_type === 'Receipt') && amount_total === null) reasons.push('amount_total required for Invoice/Receipt');\nif (document_type === 'Contract' && amount_total === null) warnings.push('amount_total missing for Contract; allowed as incomplete non-filing field');\nif (document_type === 'Other') reasons.push('document_type Other requires human review');\nif (confidence < 0.45) reasons.push('very low confidence');\nif ((document_type === 'Invoice' || document_type === 'Receipt') && currency === 'Unknown') reasons.push('currency required for Invoice/Receipt');\nif (base.status && base.status !== 'AI Pending') reasons.push('intake status not eligible for auto-file: ' + base.status);\nif (base.validation_errors) reasons.push('intake validation errors: ' + String(base.validation_errors).slice(0, 300));\nconst reviewReason = [\n  typeof ai.review_reason === 'string' ? ai.review_reason.trim().slice(0, 500) : '',\n  ...reasons,\n  ...warnings,\n].filter(Boolean).join('; ');\nconst criticalReasons = reasons.length > 0;\nconst amountConfidenceOk = document_type === 'Contract' && amount_total === null ? true : amountConfidence >= 0.75;\nconst auto_file = !criticalReasons && confidence >= 0.82 && typeConfidence >= 0.8 && partyConfidence >= 0.75 && dateConfidence >= 0.75 && amountConfidenceOk && (amount_total !== null || document_type === 'Contract') && ai.needs_human_review !== true;\nconst safeParty = (party_name || 'unknown-party').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60) || 'unknown-party';\nconst safeFilename = (base.filename || 'document').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/\\.{2,}/g, '.').replace(/^[.-]+|[.-]+$/g, '').slice(0, 100) || 'document';\nconst year = document_date ? document_date.slice(0, 4) : 'unknown-year';\nconst shortKey = base.document_key.slice(0, 12);\nconst archiveName = (document_date || 'unknown-date') + '__' + safeParty + '__' + shortKey + '__' + safeFilename;\nconst reviewName = shortKey + '__' + safeFilename;\nconst archive_path = auto_file\n  ? ['archive', document_type, year, archiveName].join('/')\n  : ['review', document_type || 'Other', reviewName].join('/');\nconst finalReviewReason = auto_file ? warnings.join('; ') : reviewReason;\nreturn { json: {\n  ...base,\n  status: auto_file ? 'Filed' : 'Needs Review',\n  document_type,\n  party_name,\n  filename_html: htmlEscape(base.filename),\n  party_name_html: htmlEscape(party_name),\n  document_date,\n  amount_total,\n  currency,\n  confidence,\n  validation_errors: reasons.join('; '),\n  review_reason: finalReviewReason,\n  review_reason_html: htmlEscape(finalReviewReason),\n  archive_path,\n  review_alert_sent: false,\n  review_alert_id: '',\n  ai_raw_json: JSON.stringify({ parsedFrom, ai }).slice(0, 9000),\n  last_error_class: auto_file ? '' : 'business',\n  last_error_message: auto_file ? '' : reviewReason,\n  auto_file,\n  updated_at: new Date().toISOString(),\n} };",
        "parameters": null
      }
    },
    {
      "id": "6122cb1b-f202-4f7e-bdfb-77fee234aaf3",
      "name": "AI Validated Auto File?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        3120,
        340
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $(\"Validate AI Extraction\").item.json.auto_file }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "33a14688-b046-46ea-b602-df6dc952f4b7",
      "name": "Insert Filed Document Record",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        3380,
        220
      ],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_TABLE_ID",
          "cachedResultName": "Intake_Documents"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "document_key": "={{ $(\"Validate AI Extraction\").item.json.document_key }}",
            "attachment_id": "={{ $(\"Validate AI Extraction\").item.json.attachment_id }}",
            "filename": "={{ $(\"Validate AI Extraction\").item.json.filename }}",
            "mime_type": "={{ $(\"Validate AI Extraction\").item.json.mime_type }}",
            "file_sha256": "={{ $(\"Validate AI Extraction\").item.json.file_sha256 }}",
            "status": "={{ $(\"Validate AI Extraction\").item.json.status }}",
            "document_type": "={{ $(\"Validate AI Extraction\").item.json.document_type }}",
            "party_name": "={{ $(\"Validate AI Extraction\").item.json.party_name }}",
            "document_date": "={{ $(\"Validate AI Extraction\").item.json.document_date }}",
            "amount_total": "={{ $(\"Validate AI Extraction\").item.json.amount_total }}",
            "currency": "={{ $(\"Validate AI Extraction\").item.json.currency }}",
            "confidence": "={{ $(\"Validate AI Extraction\").item.json.confidence }}",
            "validation_errors": "={{ $(\"Validate AI Extraction\").item.json.validation_errors }}",
            "review_reason": "={{ $(\"Validate AI Extraction\").item.json.review_reason }}",
            "archive_path": "={{ $(\"Validate AI Extraction\").item.json.archive_path }}",
            "ocr_confidence": "={{ $(\"Validate AI Extraction\").item.json.ocr_confidence }}",
            "model_transport_chars": "={{ $(\"Validate AI Extraction\").item.json.model_transport_chars }}",
            "pii_redacted_for_model": "={{ $(\"Validate AI Extraction\").item.json.pii_redacted_for_model }}",
            "review_alert_sent": "={{ $(\"Validate AI Extraction\").item.json.review_alert_sent }}",
            "review_alert_id": "={{ $(\"Validate AI Extraction\").item.json.review_alert_id }}",
            "ai_raw_json": "={{ $(\"Validate AI Extraction\").item.json.ai_raw_json }}",
            "model_prompt_excerpt": "={{ $(\"Validate AI Extraction\").item.json.model_prompt_excerpt }}",
            "raw_event_json": "={{ $(\"Validate AI Extraction\").item.json.raw_event_json }}",
            "last_execution_id": "={{ $(\"Validate AI Extraction\").item.json.last_execution_id }}",
            "last_error_class": "={{ $(\"Validate AI Extraction\").item.json.last_error_class }}",
            "last_error_message": "={{ $(\"Validate AI Extraction\").item.json.last_error_message }}",
            "created_at": "={{ $(\"Validate AI Extraction\").item.json.created_at }}",
            "updated_at": "={{ $(\"Validate AI Extraction\").item.json.updated_at }}"
          },
          "schema": [
            {
              "id": "document_key",
              "displayName": "document_key",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "attachment_id",
              "displayName": "attachment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "filename",
              "displayName": "filename",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "mime_type",
              "displayName": "mime_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "file_sha256",
              "displayName": "file_sha256",
              "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": "document_type",
              "displayName": "document_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "party_name",
              "displayName": "party_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "document_date",
              "displayName": "document_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "amount_total",
              "displayName": "amount_total",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "currency",
              "displayName": "currency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "confidence",
              "displayName": "confidence",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "validation_errors",
              "displayName": "validation_errors",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_reason",
              "displayName": "review_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "archive_path",
              "displayName": "archive_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "ocr_confidence",
              "displayName": "ocr_confidence",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "model_transport_chars",
              "displayName": "model_transport_chars",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "pii_redacted_for_model",
              "displayName": "pii_redacted_for_model",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_alert_sent",
              "displayName": "review_alert_sent",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "boolean",
              "canBeUsedToMatch": false
            },
            {
              "id": "review_alert_id",
              "displayName": "review_alert_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "ai_raw_json",
              "displayName": "ai_raw_json",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "model_prompt_excerpt",
              "displayName": "model_prompt_excerpt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "raw_event_json",
              "displayName": "raw_event_json",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_execution_id",
              "displayName": "last_execution_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_error_class",
              "displayName": "last_error_class",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "last_error_message",
              "displayName": "last_error_message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": false
            },
            {
              "id": "updated_at",
              "displayName": "updated_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": false
            }
          ]
        }
      }
    },
    {
      "id": "2f48edf5-00c4-4baf-abda-2e02afedc43b",
      "name": "Mark Document Claim Filed",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3640,
        220
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const pruneStore = (obj, max = 5000) => {\n  const keys = Object.keys(obj);\n  for (let i = 0; i < keys.length - max; i++) delete obj[keys[i]];\n};\nconst n = $('Validate AI Extraction').item.json;\nconst record = $input.item.json || {};\nconst store = $getWorkflowStaticData('global');\nstore.document_claims = store.document_claims || {};\npruneStore(store.document_claims);\nstore.document_claims[n.document_key] = { ...(store.document_claims[n.document_key] || {}), status: 'Filed', record_id: record.id || '', completed_at: new Date().toISOString(), archive_path: n.archive_path };\nreturn { json: record };"
      }
    },
    {
      "id": "0b8fe78b-62f5-4879-bd09-2ba770821507",
      "name": "Respond Document Filed",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        3900,
        220
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, status: \"filed\", document_key: $(\"Validate AI Extraction\").item.json.document_key, record_id: $json.id || \"\", document_type: $(\"Validate AI Extraction\").item.json.document_type, archive_path: $(\"Validate AI Extraction\").item.json.archive_path, confidence: $(\"Validate AI Extraction\").item.json.confidence } }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "2cd52bf5-3bc9-4757-ad64-c2fc8c7c5b64",
      "name": "Insert AI Needs Review",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        3380,
        520
      ],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_TABLE_ID",
          "cachedResultName": "Intake_Documents"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "document_key": "={{ $(\"Validate AI Extraction\").item.json.document_key }}",
            "attachment_id": "={{ $(\"Validate AI Extraction\").item.json.attachment_id }}",
            "filename": "={{ $(\"Validate AI Extraction\").item.json.filename }}",
            "mime_type": "={{ $(\"Validate AI Extraction\").item.json.mime_type }}",
            "file_sha256": "={{ $(\"Validate AI Extraction\").item.json.file_sha256 }}",
            "status": "={{ $(\"Validate AI Extraction\").item.json.status }}",
            "document_type": "={{ $(\"Validate AI Extraction\").item.json.document_type }}",
            "party_name": "={{ $(\"Validate AI Extraction\").item.json.party_name }}",
            "document_date": "={{ $(\"Validate AI Extraction\").item.json.document_date }}",
            "amount_total": "={{ $(\"Validate AI Extraction\").item.json.amount_total }}",
            "currency": "={{ $(\"Validate AI Extraction\").item.json.currency }}",
            "confidence": "={{ $(\"Validate AI Extraction\").item.json.confidence }}",
            "validation_errors": "={{ $(\"Validate AI Extraction\").item.json.validation_errors }}",
            "review_reason": "={{ $(\"Validate AI Extraction\").item.json.review_reason }}",
            "archive_path": "={{ $(\"Validate AI Extraction\").item.json.archive_path }}",
            "ocr_c
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Document Intake with AI Classification. Uses dataTable, openAi, gmail. Webhook trigger; 34 nodes.

Source: https://github.com/kuliberdalabs/n8n-sme-workflows/blob/main/workflows/03-document-intake/workflow.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Anyone who wants to automatically aggregate their invoices or receipts. Main beneficiaries: small business owners and freelancers. Creates a folder in Google Drive for uploading invoices and receipts.

OpenAI, Google Drive, Gmail +1
AI & RAG

Automatically classifies incoming cold email replies as HOT, WARM, or COLD using AI and sends priority Telegram notifications with auto-acknowledgment for fast response times.

Stop And Error, OpenAI, Telegram +2
AI & RAG

Support Triage + FAQ Draft (injection-hardened). Uses dataTable, openAi, gmail. Webhook trigger; 57 nodes.

Data Table, OpenAI, Gmail
AI & RAG

Who is this for? Event organizers, conference planners, and marketing teams fighting registration drop-off who want 4-field forms with LinkedIn-level attendee intelligence. What problem is this workfl

Data Table, HubSpot, Email Send +4
AI & RAG

This workflow delivers a complete, enterprise-grade Gmail automation system designed for high-volume teams. It classifies incoming emails, applies labels, generates AI-powered responses, and routes me

Gmail, OpenAI, Telegram +3