AutomationFlowsAI & RAG › AI-Categorize Expense Reports from Google Drive

AI-Categorize Expense Reports from Google Drive

Original n8n title: Expense Report Processor with AI Categorization

Expense Report Processor with AI Categorization. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.

Event trigger★★★★☆ complexity12 nodesGoogle Drive TriggerGoogle DriveN8N Nodes PdfvectorGoogle SheetsSlack
AI & RAG Trigger: Event Nodes: 12 Complexity: ★★★★☆ Added:

This workflow follows the Google Drive → Google Drive Trigger 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": "Expense Report Processor with AI Categorization",
  "nodes": [
    {
      "parameters": {
        "content": "AI expense receipt processor with approval routing\n\n### How it works\n1. Google Drive trigger watches your Expenses folder for new receipts\n2. PDF Vector extracts merchant, amount, date, tax, and payment method\n3. AI categorizes spending (Travel, Meals, Office Supplies, etc.)\n4. Fallback rules catch common merchants (Uber \u2192 Travel, DoorDash \u2192 Meals)\n5. All expenses logged to Google Sheets with receipt links\n6. Over $500 triggers Slack approval request, under $500 gets confirmation\n\n### Setup steps\n1. Connect Google Drive and create an \"Expenses\" folder\n2. Add PDF Vector API key from pdfvector.com/api-keys\n3. Create Google Sheet with columns: Date, Merchant, Category, Amount, Currency, Tax, Payment Method, Description, Requires Approval, Receipt Link, Processed Date\n4. Connect Slack and select notification channel\n\n### Customization\n- Change $500 threshold in Process & Categorize node\n- Add merchant rules for your vendors\n- Swap Slack for email notifications\n\n### If PDF Vector node shows Install error\n- Press Ctrl+K in the workflow editor\n- Search for PDF Vector\n- Click it to add \u2014 the node will load with all settings intact",
        "height": 560,
        "width": 340,
        "color": 5
      },
      "id": "b3b9627c-a12b-43ba-9423-5e62decd6482",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "content": "## 1. Get receipt\nDownloads new files from Expenses folder",
        "height": 120,
        "width": 280
      },
      "id": "9641b3d1-dba2-4798-b9e4-fc199139fdaf",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        384,
        128
      ]
    },
    {
      "parameters": {
        "content": "## 2. Extract & categorize\nAI reads receipt and assigns category",
        "height": 120,
        "width": 320
      },
      "id": "20bb4f6c-6b4f-432c-a232-daafe6da63df",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        720,
        128
      ]
    },
    {
      "parameters": {
        "content": "## 3. Log & route\nSaves to Sheets, routes based on amount",
        "height": 120,
        "width": 460
      },
      "id": "335a4d7e-628b-405f-9231-1f7a7c6e71a4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1104,
        128
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "value": "YOUR_FOLDER_ID",
          "mode": "list",
          "cachedResultName": "Expenses"
        },
        "event": "fileCreated",
        "options": {}
      },
      "id": "03c7c11f-1098-418c-9304-5ff3217f2524",
      "name": "Google Drive Trigger",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "typeVersion": 1,
      "position": [
        400,
        288
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "7cde84bf-1fcf-4cd5-aad8-19c628ed25ae",
      "name": "Download receipt",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        608,
        288
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "extract",
        "inputType": "file",
        "prompt": "Extract all expense details from this receipt or expense report including date, merchant name, amount, payment method, and category. If multiple items, list each separately.",
        "schema": "{\"type\":\"object\",\"properties\":{\"expenseDate\":{\"type\":\"string\"},\"merchant\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"amount\":{\"type\":\"number\"},\"currency\":{\"type\":\"string\"},\"paymentMethod\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"taxAmount\":{\"type\":\"number\"}},\"additionalProperties\":false}"
      },
      "type": "n8n-nodes-pdfvector.pdfVector",
      "typeVersion": 2,
      "position": [
        800,
        288
      ],
      "id": "438b8466-f429-425a-b031-d50997e63619",
      "name": "Extract from a document",
      "credentials": {
        "pdfVectorApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const expense = $input.first().json.data;\nconst fileName = $('Google Drive Trigger').item.json.name;\n\n// Auto-categorize if not determined\nlet category = expense.category || 'Other';\nconst merchantLower = (expense.merchant || '').toLowerCase();\n\nif (!expense.category || expense.category === 'Other') {\n  if (merchantLower.includes('uber') || merchantLower.includes('lyft') || merchantLower.includes('airline') || merchantLower.includes('hotel')) {\n    category = 'Travel';\n  } else if (merchantLower.includes('restaurant') || merchantLower.includes('cafe') || merchantLower.includes('doordash')) {\n    category = 'Meals';\n  } else if (merchantLower.includes('amazon') || merchantLower.includes('staples') || merchantLower.includes('office')) {\n    category = 'Office Supplies';\n  } else if (merchantLower.includes('software') || merchantLower.includes('saas') || merchantLower.includes('subscription')) {\n    category = 'Software';\n  }\n}\n\n// Determine if requires approval (over $500)\nconst requiresApproval = expense.amount > 500;\n\nreturn [{\n  json: {\n    ...expense,\n    category: category,\n    requiresApproval: requiresApproval,\n    fileName: fileName,\n    fileId: $('Google Drive Trigger').item.json.id,\n    processedAt: new Date().toISOString()\n  }\n}];"
      },
      "id": "aceeb7e4-ccfe-496f-aebf-9f17f252ae5b",
      "name": "Process & categorize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1008,
        288
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "list",
          "cachedResultName": "Workflow Sheet"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $json.expenseDate }}",
            "Merchant": "={{ $json.merchant }}",
            "Category": "={{ $json.category }}",
            "Amount": "={{ $json.amount }}",
            "Currency": "={{ $json.currency || 'USD' }}",
            "Tax": "={{ $json.taxAmount || 0 }}",
            "Payment Method": "={{ $json.paymentMethod || 'N/A' }}",
            "Description": "={{ $json.description || '' }}",
            "Requires Approval": "={{ $json.requiresApproval ? 'Yes' : 'No' }}",
            "Receipt Link": "=https://drive.google.com/file/d/{{ $json.fileId }}/view",
            "Processed Date": "={{ $json.processedAt.split('T')[0] }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Date",
              "displayName": "Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Merchant",
              "displayName": "Merchant",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "displayName": "Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "displayName": "Amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "displayName": "Currency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Tax",
              "displayName": "Tax",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Payment Method",
              "displayName": "Payment Method",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "displayName": "Description",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Requires Approval",
              "displayName": "Requires Approval",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Receipt Link",
              "displayName": "Receipt Link",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed Date",
              "displayName": "Processed Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "e4ae0b50-b3dd-429d-bf13-53f1e1ad3696",
      "name": "Log expense",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        1200,
        288
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "approval-condition",
              "leftValue": "={{ $('Process & categorize').item.json.requiresApproval }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8d824132-661a-4a43-a24b-6d8bff0f3792",
      "name": "Over $500?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1408,
        288
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "YOUR_SLACK_CHANNEL_ID",
          "mode": "list",
          "cachedResultName": "general"
        },
        "text": "=\ud83d\udea8 *Expense Approval Required*\n\n*Merchant:* {{ $('Process & categorize').item.json.merchant }}\n*Amount:* ${{ $('Process & categorize').item.json.amount }}\n*Category:* {{ $('Process & categorize').item.json.category }}\n*Date:* {{ $('Process & categorize').item.json.expenseDate }}\n\n<https://drive.google.com/file/d/{{ $('Process & categorize').item.json.fileId }}/view|View Receipt>",
        "otherOptions": {}
      },
      "id": "b6a77e83-76ee-4d3b-b692-8675a3f7eba9",
      "name": "Request approval",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1600,
        192
      ],
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "YOUR_SLACK_CHANNEL_ID",
          "mode": "list",
          "cachedResultName": "general"
        },
        "text": "=\u2705 *Expense Logged*\n\n*Merchant:* {{ $('Process & categorize').item.json.merchant }}\n*Amount:* ${{ $('Process & categorize').item.json.amount }}\n*Category:* {{ $('Process & categorize').item.json.category }}",
        "otherOptions": {}
      },
      "id": "7d448758-d3e1-4b30-b47a-20eb90715edd",
      "name": "Confirm logged",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1600,
        384
      ],
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Download receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download receipt": {
      "main": [
        [
          {
            "node": "Extract from a document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from a document": {
      "main": [
        [
          {
            "node": "Process & categorize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process & categorize": {
      "main": [
        [
          {
            "node": "Log expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log expense": {
      "main": [
        [
          {
            "node": "Over $500?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Over $500?": {
      "main": [
        [
          {
            "node": "Request approval",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Confirm logged",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "tags": []
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

Expense Report Processor with AI Categorization. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.

Source: https://github.com/khanhduyvt0101/workflows/blob/0153ee2efc0f692c931b9bb4c2a04abf11756822/n8n-workflows/expense-report-processor.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

Lease Agreement Analyzer for Renters. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.

Google Drive Trigger, Google Drive, N8N Nodes Pdfvector +2
AI & RAG

Financial Report Analyzer (10-K, 10-Q). Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 11 nodes.

Google Drive Trigger, Google Drive, N8N Nodes Pdfvector +2
AI & RAG

AI Contract Review & Risk Analysis. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.

Google Drive Trigger, Google Drive, N8N Nodes Pdfvector +2
AI & RAG

Patient Intake Form Processor for Healthcare. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.

Google Drive Trigger, Google Drive, N8N Nodes Pdfvector +2
AI & RAG

Analyze medical bills for errors with AI and Slack alerts. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.

Google Drive Trigger, Google Drive, N8N Nodes Pdfvector +2