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 →
{
"name": "Analyze medical bills for errors with AI and Slack alerts",
"nodes": [
{
"parameters": {
"content": "## \ud83d\udc8a Medical Bill Dispute Assistant\n\n### What this workflow does\n1. Watches Google Drive folder for medical bills\n2. Extracts line items, CPT codes, and charges\n3. AI checks for duplicate billing and overcharges\n4. Flags discrepancies between amounts\n5. Logs findings to Google Sheets\n6. Sends Slack alert with analysis\n\n### Setup steps\n1. Get PDF Vector API key from pdfvector.com/api-keys\n2. Create a Google Drive folder for bills\n3. Create a Google Sheet with these columns:\n Provider | Patient | Date of Service | Total Billed | Insurance Paid | Patient Owes | Line Items | Issues Found | Dispute Priority | Due Date | Processed Date\n4. Update the folder ID and sheet ID in the nodes\n5. Connect your Slack workspace\n\n### Perfect for\n- Patients reviewing hospital bills\n- Medical billing advocates\n- Healthcare consultants",
"height": 540,
"width": 380,
"color": 5
},
"id": "sticky-main",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
60,
60
]
},
{
"parameters": {
"content": "## \ud83d\udd00 Parallel Processing\n\nExtract and Ask run at the same time\nso both get the PDF binary data",
"height": 100,
"width": 260
},
"id": "sticky-parallel",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
840,
100
]
},
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"value": "YOUR_FOLDER_ID",
"mode": "list",
"cachedResultName": "Medical Bills"
},
"event": "fileCreated",
"options": {}
},
"id": "gdrive-trigger",
"name": "New Medical Bill",
"type": "n8n-nodes-base.googleDriveTrigger",
"typeVersion": 1,
"position": [
480,
300
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "download",
"fileId": {
"__rl": true,
"value": "={{ $json.id }}",
"mode": "id"
},
"options": {}
},
"id": "gdrive-download",
"name": "Download Bill",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
680,
300
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "extract",
"inputType": "file",
"prompt": "Extract medical bill data as flat fields. patientName, patientDob, accountNumber, insuranceProvider, policyNumber, providerName, providerAddress, providerNpi, serviceDate (YYYY-MM-DD), billingDate (YYYY-MM-DD), dueDate (YYYY-MM-DD), lineItemsList (semicolon-separated formatted as: code description billedAmount), diagnosisCodesList (semicolon-separated ICD codes with descriptions), totalBilled (number), insuranceAdjustment (number), insurancePaid (number), patientResponsibility (number), amountDue (number), potentialErrorsList (semicolon-separated potential billing errors found), duplicateCharges (true/false), upcoding (true/false), unbundling (true/false), medicallyUnnecessary (true/false).",
"schema": "{\"type\": \"object\", \"properties\": {\"patientName\": {\"type\": \"string\"}, \"patientDob\": {\"type\": \"string\"}, \"accountNumber\": {\"type\": \"string\"}, \"insuranceProvider\": {\"type\": \"string\"}, \"policyNumber\": {\"type\": \"string\"}, \"providerName\": {\"type\": \"string\"}, \"providerAddress\": {\"type\": \"string\"}, \"providerNpi\": {\"type\": \"string\"}, \"serviceDate\": {\"type\": \"string\"}, \"billingDate\": {\"type\": \"string\"}, \"dueDate\": {\"type\": \"string\"}, \"lineItemsList\": {\"type\": \"string\"}, \"diagnosisCodesList\": {\"type\": \"string\"}, \"totalBilled\": {\"type\": \"number\"}, \"insuranceAdjustment\": {\"type\": \"number\"}, \"insurancePaid\": {\"type\": \"number\"}, \"patientResponsibility\": {\"type\": \"number\"}, \"amountDue\": {\"type\": \"number\"}, \"potentialErrorsList\": {\"type\": \"string\"}, \"duplicateCharges\": {\"type\": \"boolean\"}, \"upcoding\": {\"type\": \"boolean\"}, \"unbundling\": {\"type\": \"boolean\"}, \"medicallyUnnecessary\": {\"type\": \"boolean\"}}, \"additionalProperties\": false}"
},
"id": "pdfvector-extract",
"name": "Extract Bill Data",
"type": "n8n-nodes-pdfvector.pdfVector",
"typeVersion": 2,
"position": [
900,
220
],
"credentials": {
"pdfVectorApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "ask",
"inputType": "file",
"question": "Analyze this medical bill for potential errors, overcharges, and disputable items. Check for:\n\n1. DUPLICATE BILLING: Same service billed multiple times on same date\n2. UNBUNDLING: Services that should be billed together but separated\n3. UPCODING: Higher-level codes than services warrant\n4. PHANTOM CHARGES: Services not typically associated with the procedure\n5. BALANCE BILLING: Charges exceeding allowed amounts for in-network\n6. ITEMIZED DISCREPANCIES: Unit prices that seem excessive\n7. DATE ERRORS: Services billed outside admission dates\n8. QUANTITY ERRORS: Excessive quantities for supplies/medications\n\nFor each issue found, provide:\n- Issue type\n- Specific charge(s) affected\n- Estimated overcharge amount\n- Recommended dispute action\n- Priority level (High/Medium/Low)\n\nAlso provide an overall bill health score (1-10) and total potential savings."
},
"id": "pdfvector-analyze",
"name": "Analyze for Errors",
"type": "n8n-nodes-pdfvector.pdfVector",
"typeVersion": 2,
"position": [
900,
380
],
"credentials": {
"pdfVectorApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
1120,
300
],
"id": "merge-results",
"name": "Merge Results"
},
{
"parameters": {
"jsCode": "const allInputs = $input.all();\nconst bill = (allInputs[0]?.json?.data || allInputs[0]?.json) || {};\nconst analysis = allInputs[1]?.json?.answer || allInputs[1]?.json?.markdown || '';\nconst fileName = $('New Medical Bill').item.json.name;\nconst fileId = $('New Medical Bill').item.json.id;\n\nconst errors = (bill.potentialErrorsList || '').split(';').filter(Boolean);\nconst errorsList = errors.map(e => `\u26a0\ufe0f ${e.trim()}`).join('\\n') || 'None found';\nconst flagCount = [bill.duplicateCharges, bill.upcoding, bill.unbundling, bill.medicallyUnnecessary].filter(Boolean).length;\n\nreturn [{ json: {\n patientName: bill.patientName || 'N/A',\n accountNumber: bill.accountNumber || 'N/A',\n providerName: bill.providerName || 'N/A',\n serviceDate: bill.serviceDate || 'N/A',\n totalBilled: bill.totalBilled || 0,\n amountDue: bill.amountDue || 0,\n insurancePaid: bill.insurancePaid || 0,\n errorsList, errorsCount: errors.length, flagCount,\n potentialErrorsList: bill.potentialErrorsList || 'None',\n duplicateCharges: bill.duplicateCharges || false,\n analysis,\n fileName, fileId,\n processedAt: new Date().toISOString(),\n fileId: fileId,\n dateOfService: bill.serviceDate || \"N/A\",\n admissionDate: bill.serviceDate || \"N/A\",\n dischargeDate: bill.serviceDate || \"N/A\",\n paymentDueDate: bill.dueDate || \"N/A\",\n patientResponsibility: bill.patientResponsibility || 0,\n issueCount: errors.length,\n disputePriority: errors.length > 2 ? \"High\" : errors.length > 0 ? \"Medium\" : \"Low\",\n analysisReport: analysis,\n}}];"
},
"id": "format-data",
"name": "Format Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1320,
300
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "list",
"cachedResultName": "Medical Bill Reviews"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Bills"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Provider": "={{ $json.providerName }}",
"Patient": "={{ $json.patientName }}",
"Date of Service": "={{ $json.dateOfService }}",
"Total Billed": "={{ $json.totalBilled }}",
"Insurance Paid": "={{ $json.insurancePaid }}",
"Patient Owes": "={{ $json.patientResponsibility }}",
"Line Items": "={{ $json.lineItemCount }}",
"Issues Found": "={{ $json.issueCount }}",
"Dispute Priority": "={{ $json.disputePriority }}",
"Due Date": "={{ $json.paymentDueDate }}",
"Processed Date": "={{ $json.processedAt.split('T')[0] }}",
"Service Date": "={{ $json.serviceDate }}",
"Amount Due": "={{ $json.amountDue }}",
"Errors Found": "={{ $json.errorsCount }}",
"Flag Count": "={{ $json.flagCount }}",
"Document Link": "=https://drive.google.com/file/d/{{ $json.fileId }}/view",
"Added Date": "={{ $json.processedAt.split('T')[0] }}"
},
"matchingColumns": [],
"schema": [
{
"id": "Provider",
"displayName": "Provider",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Patient",
"displayName": "Patient",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Date of Service",
"displayName": "Date of Service",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Total Billed",
"displayName": "Total Billed",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Insurance Paid",
"displayName": "Insurance Paid",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Patient Owes",
"displayName": "Patient Owes",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Line Items",
"displayName": "Line Items",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Issues Found",
"displayName": "Issues Found",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Dispute Priority",
"displayName": "Dispute Priority",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Due Date",
"displayName": "Due Date",
"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": "sheets-log",
"name": "Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.4,
"position": [
1520,
300
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "YOUR_SLACK_CHANNEL_ID",
"mode": "list",
"cachedResultName": "billing-reviews"
},
"text": "=\ud83d\udc8a *Medical Bill Analysis Complete*\n\n*Provider:* {{ $('Format Results').item.json.providerName }}\n*Patient:* {{ $('Format Results').item.json.patientName }}\n*Account:* {{ $('Format Results').item.json.accountNumber }}\n\n\ud83d\udcc5 *Service Details:*\n\u2022 Date: {{ $('Format Results').item.json.dateOfService }}\n\u2022 Admission: {{ $('Format Results').item.json.admissionDate }}\n\u2022 Discharge: {{ $('Format Results').item.json.dischargeDate }}\n\n\ud83d\udcb0 *Charges:*\n\u2022 Total Billed: ${{ $('Format Results').item.json.totalBilled }}\n\u2022 Insurance Paid: ${{ $('Format Results').item.json.insurancePaid }}\n\u2022 *Patient Owes: ${{ $('Format Results').item.json.patientResponsibility }}*\n\u2022 Due: {{ $('Format Results').item.json.paymentDueDate }}\n\n\ud83d\udd0d *Analysis:*\n\u2022 Issues Found: {{ $('Format Results').item.json.issueCount }}\n\u2022 Priority: {{ $('Format Results').item.json.disputePriority }}\n\n\ud83d\udcdd *AI Report:*\n{{ $('Format Results').item.json.analysisReport }}\n\n\ud83d\udd17 <https://drive.google.com/file/d/{{ $('Format Results').item.json.fileId }}/view|View Bill>",
"otherOptions": {}
},
"id": "slack-notify",
"name": "Send to Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
1720,
300
],
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
}
}
],
"connections": {
"New Medical Bill": {
"main": [
[
{
"node": "Download Bill",
"type": "main",
"index": 0
}
]
]
},
"Download Bill": {
"main": [
[
{
"node": "Extract Bill Data",
"type": "main",
"index": 0
},
{
"node": "Analyze for Errors",
"type": "main",
"index": 0
}
]
]
},
"Extract Bill Data": {
"main": [
[
{
"node": "Merge Results",
"type": "main",
"index": 0
}
]
]
},
"Analyze for Errors": {
"main": [
[
{
"node": "Merge Results",
"type": "main",
"index": 1
}
]
]
},
"Merge Results": {
"main": [
[
{
"node": "Format Results",
"type": "main",
"index": 0
}
]
]
},
"Format Results": {
"main": [
[
{
"node": "Log to Sheets",
"type": "main",
"index": 0
}
]
]
},
"Log to Sheets": {
"main": [
[
{
"node": "Send to Slack",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"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.
googleDriveOAuth2ApigoogleSheetsOAuth2ApipdfVectorApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Analyze medical bills for errors with AI and Slack alerts. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.
Source: https://github.com/khanhduyvt0101/workflows/blob/0153ee2efc0f692c931b9bb4c2a04abf11756822/n8n-workflows/medical-bill-analyzer.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Lease Agreement Analyzer for Renters. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.
Expense Report Processor with AI Categorization. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.
Financial Report Analyzer (10-K, 10-Q). Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 11 nodes.
AI Contract Review & Risk Analysis. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.
Patient Intake Form Processor for Healthcare. Uses googleDriveTrigger, googleDrive, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.