This workflow follows the Chainllm → HTTP Request 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": "Helpdesk Casus \u2013 Route 1 (HTTP CSV) [v2 LLM]",
"nodes": [
{
"parameters": {},
"name": "Start",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-5424,
1008
],
"id": "e91dcef0-b70e-4fc3-b657-7739ed4de55b"
},
{
"parameters": {
"url": "https://raw.githubusercontent.com/Loekie1103/educom-helpdesk-n8n-loek/main/casus_data/demo-data/tickets.csv",
"responseFormat": "file",
"options": {}
},
"name": "HTTP Request - Get tickets.csv",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
-4752,
1008
],
"id": "560026e2-b216-461c-be6a-ad19b075ad46"
},
{
"parameters": {
"options": {
"headerRow": true
}
},
"name": "Parse CSV",
"type": "n8n-nodes-base.spreadsheetFile",
"typeVersion": 1,
"position": [
-4528,
1008
],
"id": "90198f0e-4476-4e30-b62d-d651d6f3d92a"
},
{
"parameters": {
"url": "https://raw.githubusercontent.com/Loekie1103/educom-helpdesk-n8n-loek/main/casus_data/config/categories.json",
"options": {}
},
"name": "HTTP - Get categories.json",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
-5200,
1008
],
"id": "609145d5-f96e-487a-8f26-9dec56ed0754"
},
{
"parameters": {
"url": "https://raw.githubusercontent.com/Loekie1103/educom-helpdesk-n8n-loek/main/casus_data/config/queues.json",
"options": {}
},
"name": "HTTP - Get queues.json",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
-4976,
1008
],
"id": "7a67cc21-ae47-4e38-89d2-8dfd068b6ec8"
},
{
"parameters": {
"jsCode": "/**\n * P2: PII Redaction & Date Formatting\n */\nfunction excelDateToISO(serial) {\n const num = parseFloat(serial);\n if (isNaN(num)) return serial;\n const excelEpoch = new Date(1899, 11, 30);\n const days = Math.floor(num);\n const ms = Math.round((num - days) * 24 * 60 * 60 * 1000);\n const date = new Date(excelEpoch.getTime() + days * 24 * 60 * 60 * 1000 + ms);\n return date.toISOString().replace('T', ' ').substring(0, 19);\n}\nfunction fnvHash(str) {\n let h = 14695981039346656037n;\n for (let i = 0; i < str.length; i++) { h ^= BigInt(str.charCodeAt(i)); h *= 1099511628211n; }\n return h.toString(16).padStart(16, '0').substring(0, 16);\n}\nconst allIds = items.map(item => item.json.id || '').sort().join('|');\nconst runId = 'RUN-' + fnvHash(allIds + '|' + items.length).toUpperCase();\nreturn items.map(item => {\n let body = item.json.body || '';\n let subject = item.json.subject || '';\n let created_at = item.json.created_at || '';\n if (created_at && !isNaN(parseFloat(created_at))) { created_at = excelDateToISO(created_at); }\n const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n const phoneRegex = /(?:\\+31|0)(?:\\s?\\d){9}/g;\n const bsnRegex = /\\b\\d{9}\\b/g;\n body = body.replace(emailRegex, '[EMAIL_REDACTED]');\n body = body.replace(phoneRegex, '[PHONE_REDACTED]');\n body = body.replace(bsnRegex, '[BSN_REDACTED]');\n subject = subject.replace(emailRegex, '[EMAIL_REDACTED]');\n subject = subject.replace(phoneRegex, '[PHONE_REDACTED]');\n subject = subject.replace(bsnRegex, '[BSN_REDACTED]');\n return { json: { ...item.json, run_id: runId, created_at, body, subject } };\n});"
},
"name": "P2 - Redact PII",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-4304,
1008
],
"id": "59373faa-2c0f-481f-9171-5b889e9aa50d"
},
{
"parameters": {
"jsCode": "// 1. Fetch categories\nconst categoriesConfig = $('HTTP - Get categories.json').first().json;\nif (!categoriesConfig || !categoriesConfig.categories) {\n throw new Error('Failed to load categories.json');\n}\n\n// 2. Build the category list for the LLM\nconst catList = categoriesConfig.categories\n .map(c => `${c.id}: ${c.name} - ${c.description}`)\n .join('\\n');\n\n// 3. Define a strict System Prompt\nconst systemPrompt = `You are an IT Helpdesk triage assistant. Categorize the following ticket into exactly ONE of these categories:\\n${catList}\\n\\nYou must respond ONLY with a valid, raw JSON object using this exact schema. Do not add markdown formatting:\\n{\"category\": \"CAT_ID_HERE\", \"confidence\": 0.95, \"reason\": \"Short reason here\"}`;\n\n// 4. Filter only ticket items (ignore the config inputs)\nconst ticketItems = items.filter(item => item.json && item.json.id);\n\n// LIMIT FOR TESTING: Only take the first 5 tickets\nconst testTickets = [...ticketItems].sort(() => 0.5 - Math.random()).slice(0, 20);\n\n// 5. Output the ticket data and the final prompt\nreturn testTickets.map(item => {\n const t = item.json;\n const userPrompt = `Subject: ${t.subject || 'N/A'}\\nBody: ${t.body || 'N/A'}`;\n const fullPrompt = `${systemPrompt}\\n\\nTicket Details:\\n${userPrompt}`;\n\n return {\n json: {\n _ticket: t,\n prompt: fullPrompt,\n }\n }\n });"
},
"name": "P3a - Prepare LLM Messages",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-4080,
1008
],
"id": "8fab70f2-26bf-4b27-a4c5-2665ede4208f"
},
{
"parameters": {
"jsCode": "/**\n * P3c: Parse OpenAI Response & Route\n * P4: Generate draft reply for misrouted tickets\n */\nconst categoriesConfig = $('HTTP - Get categories.json').first().json;\nconst queuesConfig = $('HTTP - Get queues.json').first().json;\n\nif (!categoriesConfig || !categoriesConfig.categories) throw new Error('Failed to load categories.json');\nif (!queuesConfig || !queuesConfig.queues) throw new Error('Failed to load queues.json');\n\nconst validQueues = new Set(queuesConfig.queues.map(q => q.id));\nconst validCategories = new Set(categoriesConfig.categories.map(c => c.id));\n\nconst routingMap = {\n 'CAT_ACCESS_ACCOUNT':'Q_IDENTITY_ACCESS','CAT_M365_COPILOT':'Q_M365',\n 'CAT_GH_COPILOT':'Q_DEVELOPER_EXPERIENCE','CAT_SOFTWARE_INSTALL':'Q_WORKPLACE',\n 'CAT_VPN_REMOTE':'Q_NETWORK','CAT_NETWORK_WIFI':'Q_NETWORK',\n 'CAT_HARDWARE_DEVICE':'Q_WORKPLACE','CAT_PRINTER':'Q_WORKPLACE',\n 'CAT_ONBOARDING_HR':'Q_HR','CAT_PROCUREMENT_BILLING':'Q_FINANCE_PROCUREMENT',\n 'CAT_OTHER':'Q_IT_SERVICE_DESK'\n};\n\n// Keyword-based fallback triage\nfunction keywordTriage(subject, body) {\n const content = ((subject || '') + ' ' + (body || '')).toLowerCase();\n let bestCat = null;\n let maxMatches = 0;\n categoriesConfig.categories.forEach(cat => {\n let matches = 0;\n (cat.keywords || []).forEach(kw => {\n if (content.includes(kw.toLowerCase())) matches++;\n });\n if (matches > maxMatches) { maxMatches = matches; bestCat = cat; }\n });\n if (bestCat && maxMatches > 0) {\n const confidence = maxMatches >= 3 ? 0.85 : maxMatches >= 2 ? 0.65 : 0.50;\n const routeTo = routingMap[bestCat.id] || 'Q_IT_SERVICE_DESK';\n return { category: bestCat.id, confidence, reason: 'Keyword fallback: matched ' + maxMatches + ' keyword(s) for ' + bestCat.id + '.', route_to: routeTo, triage_source: 'keyword_fallback' };\n }\n return { category: 'CAT_OTHER', confidence: 0.40, reason: 'No keywords matched, defaulting to CAT_OTHER.', route_to: 'Q_IT_SERVICE_DESK', triage_source: 'keyword_fallback' };\n}\n\n// Robust JSON extractor from LLM text output\nfunction extractJSON(text) {\n if (!text) return null;\n try {\n // Strip markdown code blocks if the LLM adds them\n let cleanText = text.replace(/```json\\s*/gi, '').replace(/```\\s*/gi, '').trim();\n // Try direct parse first\n try { return JSON.parse(cleanText); } catch(e) {}\n // Try to find JSON object with regex\n const match = cleanText.match(/\\{[^{}]*\"category\"[^{}]*\\}/);\n if (match) {\n try { return JSON.parse(match[0]); } catch(e) {}\n }\n return null;\n } catch(e) {\n return null;\n }\n}\n\n// Generate draft reply for misrouted tickets (P4)\nfunction generateDraftReply(category, routeTo) {\n const queueName = queuesConfig.queues.find(q => q.id === routeTo);\n const target = queueName ? queueName.name : routeTo;\n const catName = categoriesConfig.categories.find(c => c.id === category);\n const catLabel = catName ? catName.name : category;\n return 'Beste medewerker,\\n\\nBedankt voor uw melding. We hebben uw ticket gecategoriseerd onder \"' + catLabel + '\" en doorgezet naar het specialistische team: ' + target + '. Zij nemen dit zo snel mogelijk in behandeling.\\n\\nMet vriendelijke groet,\\nIT Service Desk Support';\n}\n\n// Process each ticket that came through the LLM chain\nreturn items.map(item => {\n const ticket = item.json._ticket || {};\n const llmText = item.json.text || item.json.response || '';\n let result;\n\n try {\n const parsed = extractJSON(llmText);\n if (parsed && parsed.category && validCategories.has(parsed.category)) {\n const routeTo = routingMap[parsed.category] || 'Q_IT_SERVICE_DESK';\n if (!validQueues.has(routeTo)) throw new Error('Invalid queue: ' + routeTo);\n result = {\n category: parsed.category,\n confidence: typeof parsed.confidence === 'number' ? parsed.confidence : parseFloat(parsed.confidence) || 0.80,\n route_to: routeTo,\n route_reason: parsed.reason || 'LLM classified as ' + parsed.category + '.',\n triage_source: 'llm'\n };\n } else {\n // LLM returned invalid/missing category \u2014 fall back to keyword triage\n result = keywordTriage(ticket.subject, ticket.body);\n }\n } catch(e) {\n // Exception during parsing \u2014 fall back to keyword triage\n result = keywordTriage(ticket.subject, ticket.body);\n result.triage_source = 'error';\n }\n\n // Generate draft reply for tickets routed away from main desk (P4)\n let draft_reply = '';\n if (result.route_to !== 'Q_IT_SERVICE_DESK') {\n draft_reply = generateDraftReply(result.category, result.route_to);\n }\n\n return {\n json: {\n ...ticket,\n category: result.category,\n confidence: result.confidence,\n route_to: result.route_to,\n route_reason: result.route_reason,\n draft_reply: draft_reply,\n triage_source: result.triage_source\n }\n };\n});"
},
"name": "P3c - Parse OpenAI Response & Route",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-3504,
1008
],
"id": "c18f878a-6e4b-4841-a352-c5c1c3415cf8"
},
{
"parameters": {
"operation": "summarize",
"fieldsToSummarize": {
"values": [
{
"field": "category"
}
]
},
"fieldsToSplitBy": "category",
"options": {}
},
"name": "P5 - Group Categories (Pareto Count)",
"type": "n8n-nodes-base.itemLists",
"typeVersion": 2.1,
"position": [
-3280,
912
],
"id": "9d5fcdaf-64e0-447d-9e16-86068cb6b364"
},
{
"parameters": {
"sortFieldsUi": {
"sortField": [
{
"fieldName": "count_category",
"order": "descending"
}
]
},
"options": {}
},
"name": "P5 - Sort Categories Descending",
"type": "n8n-nodes-base.sort",
"typeVersion": 1,
"position": [
-3056,
912
],
"id": "f472ec4b-6d5a-4731-a151-8746d15b1b10"
},
{
"parameters": {
"values": {
"number": [
{
"name": "percentage",
"value": "={{ Math.round(($json.count_category / $('P3c - Parse OpenAI Response & Route').all().length) * 100) || 0 }}"
}
],
"string": [
{
"name": "category",
"value": "={{ $json.category }}"
}
]
},
"options": {}
},
"name": "P5 - Calculate Percentages",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [
-2832,
912
],
"id": "ff79317c-8da6-48fd-98da-a6a76f4325b6"
},
{
"parameters": {
"jsCode": "/**\n * P6 Trend & Drift Analysis\n */\nconst triageNode = $('P3c - Parse OpenAI Response & Route');\nconst allTickets = triageNode.all().map(item => item.json);\nallTickets.sort((a, b) => { const dateA = new Date(a.created_at || 0); const dateB = new Date(b.created_at || 0); return dateA - dateB; });\nconst midPoint = Math.floor(allTickets.length / 2);\nconst period1 = allTickets.slice(0, midPoint);\nconst period2 = allTickets.slice(midPoint);\nconst p1Counts = {}; const p2Counts = {};\nperiod1.forEach(t => { const cat = t.category || 'CAT_OTHER'; p1Counts[cat] = (p1Counts[cat] || 0) + 1; });\nperiod2.forEach(t => { const cat = t.category || 'CAT_OTHER'; p2Counts[cat] = (p2Counts[cat] || 0) + 1; });\nconst allCategories = new Set([...Object.keys(p1Counts), ...Object.keys(p2Counts)]);\nconst trends = [];\nallCategories.forEach(cat => { const count1 = p1Counts[cat] || 0; const count2 = p2Counts[cat] || 0; const diff = count2 - count1; let signal = 'stable'; if (diff > 1) signal = 'riser'; else if (diff < -1) signal = 'faller'; trends.push({ category: cat, period1_count: count1, period2_count: count2, difference: diff, signal: signal }); });\ntrends.sort((a, b) => Math.abs(b.difference) - Math.abs(a.difference));\nreturn trends.map(t => ({ json: t }));"
},
"name": "P6 - Trend & Drift Analysis",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-2608,
912
],
"id": "4f942b5c-e918-4029-809d-b14897a69627"
},
{
"parameters": {
"jsCode": "/**\n * P7 & P8: Observability Report\n */\nconst runId = $('P2 - Redact PII').first().json.run_id;\nconst triageNode = $('P3c - Parse OpenAI Response & Route');\nconst allTickets = triageNode.all().map(item => item.json);\nconst totalProcessed = allTickets.length;\nconst totalRouted = allTickets.filter(t => t.route_to !== 'Q_IT_SERVICE_DESK').length;\nconst totalErrors = allTickets.filter(t => t.triage_source === 'error').length;\nconst triageStats = { llm: allTickets.filter(t => t.triage_source === 'llm').length, keyword_fallback: allTickets.filter(t => t.triage_source === 'keyword_fallback').length, error: allTickets.filter(t => t.triage_source === 'error').length };\nconst catCounts = {};\nallTickets.forEach(t => { const cat = t.category || 'CAT_OTHER'; catCounts[cat] = (catCounts[cat] || 0) + 1; });\nconst pareto = $('P5 - Calculate Percentages').all().map(item => item.json);\nconst trends = $('P6 - Trend & Drift Analysis').all().map(item => item.json);\nreturn [{ json: { run_id: runId, timestamp: new Date().toISOString(), summary_processed: totalProcessed, summary_errors: totalErrors, summary_routed: totalRouted, triage_stats: triageStats, category_counts: catCounts, pareto_analysis: pareto, trends: trends } }];"
},
"name": "P7 & P8 - Compile Observability Report",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-2384,
912
],
"id": "bc65badf-0bcc-43bc-b5c4-e7971d110163"
},
{
"parameters": {
"jsCode": "/**\n * P7 Routed Tickets\n */\nconst triageNode = $('P3c - Parse OpenAI Response & Route');\nconst allTickets = triageNode.all().map(item => item.json);\nfunction fnvHash(str) { let h = 14695981039346656037n; for (let i = 0; i < str.length; i++) { h ^= BigInt(str.charCodeAt(i)); h *= 1099511628211n; } return h.toString(16).padStart(16, '0').substring(0, 16); }\nconst routedTickets = allTickets.filter(t => t.route_to !== 'Q_IT_SERVICE_DESK').map(t => ({ id: t.id, created_at: t.created_at, channel: t.channel, language: t.language, requester_type: t.requester_type, subject: t.subject, category: t.category, confidence: t.confidence, route_to: t.route_to, route_reason: t.route_reason, triage_source: t.triage_source, draft_reply: t.draft_reply, body_fnva_hash: t.body ? fnvHash(t.body) : '' }));\nreturn routedTickets.map(t => ({ json: t }));"
},
"name": "P7 - Compile Routed Tickets",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-3280,
1104
],
"id": "57eeaa67-e80f-4f2f-90fc-722ebb7f166f"
},
{
"parameters": {
"resource": "file",
"operation": "edit",
"owner": {
"__rl": true,
"value": "Loekie1103",
"mode": "list",
"cachedResultName": "Loekie1103",
"cachedResultUrl": "https://github.com/Loekie1103"
},
"repository": {
"__rl": true,
"value": "educom-helpdesk-n8n-loek",
"mode": "list",
"cachedResultName": "educom-helpdesk-n8n-loek",
"cachedResultUrl": "https://github.com/Loekie1103/educom-helpdesk-n8n-loek"
},
"filePath": "reports/latest.json",
"fileContent": "={{ JSON.stringify($json, null, 2) }}",
"commitMessage": "=Update report - {{ $json.timestamp }}"
},
"type": "n8n-nodes-base.github",
"typeVersion": 1.1,
"position": [
-2160,
816
],
"id": "ada42d1b-c8f9-4f39-8fd0-4ec7ea24dca3",
"name": "Edit a file1",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "file",
"operation": "edit",
"owner": {
"__rl": true,
"value": "Loekie1103",
"mode": "list",
"cachedResultName": "Loekie1103",
"cachedResultUrl": "https://github.com/Loekie1103"
},
"repository": {
"__rl": true,
"value": "educom-helpdesk-n8n-loek",
"mode": "list",
"cachedResultName": "educom-helpdesk-n8n-loek",
"cachedResultUrl": "https://github.com/Loekie1103/educom-helpdesk-n8n-loek"
},
"filePath": "reports/routed_tickets.json",
"fileContent": "={{ JSON.stringify($json, null, 2) }}",
"commitMessage": "=Routed tickets"
},
"type": "n8n-nodes-base.github",
"typeVersion": 1.1,
"position": [
-2832,
1104
],
"id": "6fcf66e5-67eb-4fa0-ba35-c772945db0f4",
"name": "Edit a file",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "Routed tickets",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
-3056,
1104
],
"id": "583fdca0-1cda-48f8-ae0a-e262b3f119e7",
"name": "Aggregate"
},
{
"parameters": {
"jsCode": "/**\n * P7: Format Markdown Report\n */\nconst latestNode = $('P7 & P8 - Compile Observability Report');\nconst reportJson = latestNode.first().json;\nconst runId = reportJson.run_id;\nconst ts = reportJson.timestamp;\nconst processed = reportJson.summary_processed;\nconst routed = reportJson.summary_routed;\nconst errors = reportJson.summary_errors;\nconst pareto = reportJson.pareto_analysis || [];\nconst trends = reportJson.trends || [];\nconst catCounts = reportJson.category_counts || {};\nconst triageStats = reportJson.triage_stats || {};\nconst catNames = { 'CAT_ACCESS_ACCOUNT':'Access & Account','CAT_M365_COPILOT':'M365 Copilot','CAT_GH_COPILOT':'GitHub Copilot','CAT_SOFTWARE_INSTALL':'Software Install','CAT_VPN_REMOTE':'VPN & Remote','CAT_NETWORK_WIFI':'Network & Wi-Fi','CAT_HARDWARE_DEVICE':'Device & Hardware','CAT_PRINTER':'Printer & Scanning','CAT_ONBOARDING_HR':'Onboarding / HR','CAT_PROCUREMENT_BILLING':'Procurement / Billing','CAT_OTHER':'Overig / Onbekend' };\nconst cn = id => catNames[id] || id;\nlet md = '# Helpdesk Report \u2014 ' + runId + '\\n\\n';\nmd += '**Report date:** ' + ts + '\\n';\nmd += '**Tickets processed:** ' + processed + ' | **Routed:** ' + routed + ' | **Errors:** ' + errors + '\\n';\nif (triageStats.llm !== undefined) { md += '**Triage source:** LLM: ' + (triageStats.llm || 0) + ' | Keyword fallback: ' + (triageStats.keyword_fallback || 0) + ' | Error: ' + (triageStats.error || 0) + '\\n'; }\nmd += '\\n## Pareto Analysis\\n\\n| # | Category | Count | % |\\n|---|----------|-------|---|\\n';\npareto.forEach((p, i) => { md += '| ' + (i+1) + ' | ' + cn(p.category) + ' | ' + p.count_category + ' | ' + p.percentage + '% |\\n'; });\nmd += '\\n## Trends\\n\\n| Category | Period 1 | Period 2 | Delta | Signal |\\n|----------|---------|---------|-------|--------|\\n';\ntrends.forEach(t => { var signal = t.signal === 'riser' ? 'Riser \u2b06' : t.signal === 'faller' ? 'Faller \u2b07' : 'Stable \u2795'; md += '| ' + cn(t.category) + ' | ' + t.period1_count + ' | ' + t.period2_count + ' | ' + (t.difference > 0 ? '+' : '') + t.difference + ' | ' + signal + ' |\\n'; });\nmd += '\\n## Category Breakdown\\n\\n| Category | Count | % |\\n|----------|-------|---|\\n';\nObject.entries(catCounts).sort(function(a,b) { return b[1]-a[1]; }).forEach(function(entry) { var cat = entry[0]; var count = entry[1]; var pct = processed > 0 ? Math.round(count / processed * 100) : 0; md += '| ' + cn(cat) + ' | ' + count + ' | ' + pct + '% |\\n'; });\nmd += '\\n---\\n_Generated by n8n Helpdesk Casus \u2014 Route 1 (HTTP CSV) [v2 LLM]_\\n';\nreturn [{ json: { content: md } }];"
},
"name": "P7 - Format Markdown Report",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
-2160,
1008
],
"id": "864c5c64-0262-4bfe-b2a9-8e1aa2d21510"
},
{
"parameters": {
"resource": "file",
"operation": "edit",
"owner": {
"__rl": true,
"value": "Loekie1103",
"mode": "list",
"cachedResultName": "Loekie1103",
"cachedResultUrl": "https://github.com/Loekie1103"
},
"repository": {
"__rl": true,
"value": "educom-helpdesk-n8n-loek",
"mode": "list",
"cachedResultName": "educom-helpdesk-n8n-loek",
"cachedResultUrl": "https://github.com/Loekie1103/educom-helpdesk-n8n-loek"
},
"filePath": "reports/report.md",
"fileContent": "={{ $json.content }}",
"commitMessage": "=Update report.md - {{ $('P7 & P8 - Compile Observability Report').first().json.run_id }}"
},
"type": "n8n-nodes-base.github",
"typeVersion": 1.1,
"position": [
-1936,
1008
],
"id": "943eea43-faa2-402e-8f4d-37f07b457f92",
"name": "Edit a file (report.md)",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": {
"__rl": true,
"value": "openrouter/google/gemini-2.5-flash",
"mode": "list",
"cachedResultName": "openrouter/google/gemini-2.5-flash"
},
"builtInTools": {},
"options": {
"maxTokens": 150,
"temperature": 0.1
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
-3776,
1184
],
"id": "8dd38e36-0aab-458b-84dc-f96b928469ea",
"name": "OpenAI Chat Model",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}",
"batching": {
"batchSize": 1
}
},
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.9,
"position": [
-3856,
1008
],
"id": "11dc8759-511f-4b4b-a5f5-26437359d84c",
"name": "P3b - Triage"
}
],
"connections": {
"Start": {
"main": [
[
{
"node": "HTTP - Get categories.json",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Get tickets.csv": {
"main": [
[
{
"node": "Parse CSV",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Get categories.json": {
"main": [
[
{
"node": "HTTP - Get queues.json",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Get queues.json": {
"main": [
[
{
"node": "HTTP Request - Get tickets.csv",
"type": "main",
"index": 0
}
]
]
},
"Parse CSV": {
"main": [
[
{
"node": "P2 - Redact PII",
"type": "main",
"index": 0
}
]
]
},
"P2 - Redact PII": {
"main": [
[
{
"node": "P3a - Prepare LLM Messages",
"type": "main",
"index": 0
}
]
]
},
"P3a - Prepare LLM Messages": {
"main": [
[
{
"node": "P3b - Triage",
"type": "main",
"index": 0
}
]
]
},
"P3c - Parse OpenAI Response & Route": {
"main": [
[
{
"node": "P5 - Group Categories (Pareto Count)",
"type": "main",
"index": 0
},
{
"node": "P7 - Compile Routed Tickets",
"type": "main",
"index": 0
}
]
]
},
"P5 - Group Categories (Pareto Count)": {
"main": [
[
{
"node": "P5 - Sort Categories Descending",
"type": "main",
"index": 0
}
]
]
},
"P5 - Sort Categories Descending": {
"main": [
[
{
"node": "P5 - Calculate Percentages",
"type": "main",
"index": 0
}
]
]
},
"P5 - Calculate Percentages": {
"main": [
[
{
"node": "P6 - Trend & Drift Analysis",
"type": "main",
"index": 0
}
]
]
},
"P6 - Trend & Drift Analysis": {
"main": [
[
{
"node": "P7 & P8 - Compile Observability Report",
"type": "main",
"index": 0
}
]
]
},
"P7 & P8 - Compile Observability Report": {
"main": [
[
{
"node": "Edit a file1",
"type": "main",
"index": 0
},
{
"node": "P7 - Format Markdown Report",
"type": "main",
"index": 0
}
]
]
},
"P7 - Compile Routed Tickets": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Edit a file",
"type": "main",
"index": 0
}
]
]
},
"P7 - Format Markdown Report": {
"main": [
[
{
"node": "Edit a file (report.md)",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "P3b - Triage",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"P3b - Triage": {
"main": [
[
{
"node": "P3c - Parse OpenAI Response & Route",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"timeSavedMode": "fixed",
"timezone": "Europe/Amsterdam",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"versionId": "62554858-39ae-46de-9175-86ed00a33558",
"meta": {
"templateCredsSetupCompleted": true
},
"nodeGroups": [],
"id": "gz7qNyxnPUa87hrk",
"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.
githubApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Helpdesk Casus – Route 1 (HTTP CSV) [v2 LLM]. Uses httpRequest, spreadsheetFile, itemLists, github. Event-driven trigger; 21 nodes.
Source: https://github.com/Loekie1103/educom-helpdesk-n8n-loek/blob/1870ab96ae838f89031a4dc31ca5bcf2eb97d2c6/n8n/workflow-export.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.
simon-apify-youtube-transcript-20250724-v1-ok. Uses httpRequest, lmChatOpenAi, chainLlm, executeWorkflowTrigger. Event-driven trigger; 19 nodes.
Wait Splitout. Uses stickyNote, httpRequest, formTrigger, googleSheets. Event-driven trigger; 17 nodes.
Wait Splitout. Uses stickyNote, httpRequest, formTrigger, googleSheets. Event-driven trigger; 16 nodes.
Manual Http. Uses manualTrigger, salesforce, itemLists, renameKeys. Event-driven trigger; 14 nodes.
Workflow 1794. Uses salesforce, itemLists, httpRequest, spreadsheetFile. Event-driven trigger; 14 nodes.