This workflow corresponds to n8n.io template #8713 — we link there as the canonical source.
This workflow follows the Googlegemini → 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 →
{
"id": "oSPXtQOZoHDhoaDT",
"name": "Support Triage workflow - to publish",
"tags": [],
"nodes": [
{
"id": "7a738865-d41d-4fef-aa5d-421f75fcc622",
"name": "SET_SETUP",
"type": "n8n-nodes-base.set",
"position": [
256,
-240
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c90a8acc-f2b5-486d-aec1-52e9f53573cc",
"name": "CONF_THRESH",
"type": "string",
"value": "0.7"
},
{
"id": "7fdb72d5-0233-492c-8058-b99ae412c121",
"name": "JIRA_AI_LABEL",
"type": "string",
"value": "triaged:ai"
},
{
"id": "41cec41d-a671-4e62-910f-1c1909c9ac70",
"name": "domain",
"type": "string",
"value": "{ \"keywords\": { \"boards\": [ \"create board\", \"board not found\", \"board update\", \"private board\", \"board API error\", \"permissionLevel\", \"organization board\" ], \"cards\": [ \"card not created\", \"card update failed\", \"attachments\", \"custom field\", \"labels\", \"due date\", \"checklist\" ], \"lists\": [ \"list order\", \"archive list\", \"list not found\", \"create list\", \"move list\", \"GET lists\", \"closed list\" ], \"members\": [ \"member info\", \"username update\", \"stale profile\", \"API key\", \"2FA\", \"account details\", \"authorization\", \"scope ignored\", \"security breach\" ], \"integrations\": [ \"webhook\", \"Jira sync\", \"Slack sync\", \"delay\", \"retry count\", \"missing payload\", \"API integration error\", \"marketplace unavailable\" ], \"outage\": [ \"checkout down\", \"site unavailable\", \"cannot access\", \"all users affected\", \"service unavailable\", \"api unavailable\", \"production down\", \"system-wide failure\", \"security issue\", \"critical vulnerability\" ] }, \"guidance_addons\": { \"Boards\": [ \"Verify if the user\u2019s API key has the correct scopes for board creation\", \"Ask if the error occurs only for private vs. public boards\", \"Check if the organization ID is passed correctly in the payload\", \"Review Trello status page for regional board service incidents\", \"Confirm board limit (10,000) not exceeded for the tenant\" ], \"Cards\": [ \"Request failing card IDs and timestamps of API calls\", \"Check attachment file type and size against Trello\u2019s documented limits\", \"Verify if the custom field schema is valid and published\", \"Review if the card update works via UI but fails via API for comparison\", \"Ask if errors correlate with rate limit spikes or throttling events\" ], \"Lists\": [ \"Confirm list IDs being used are from the correct board\", \"Check if client-side sorting logic is applied after API call\", \"Request exact payloads sent to PUT /1/lists/{id}/closed\", \"Verify if archived lists appear correctly when fetching with ?filter=all\", \"Compare response between API and UI order to detect cache issues\" ], \"Members\": [ \"Ask for affected member IDs and when the profile update occurred\", \"Check if 2FA was enabled before API key issuance\", \"Request logs showing which fields remain stale after update\", \"Verify if user is using API token vs. OAuth session\", \"Cross-check with Trello audit logs for account security events\" ], \"Integrations\": [ \"Collect webhook IDs, retry counts, and response codes for failed deliveries\", \"Ask if issue affects all webhooks or only Jira/Slack integrations\", \"Check Trello status page for webhook delivery delays or outages\", \"Verify if payloads include required fields (e.g., comment text, action type)\", \"Confirm downstream systems (Jira/Slack) aren\u2019t rejecting payloads\" ] }, \"components\": [ \"Boards\", \"Cards\", \"Lists\", \"Members\", \"Integrations\", \"Other\" ], \"priority_policy\": [\"Highest\", \"High\", \"Medium\", \"Low\"], \"priority_rules\": { \"Highest\": \"Triggered if outage keywords match OR system-wide API failures (all tenants, critical path) OR confirmed security vulnerabilities (2FA, API key bypass, scope ignored).\", \"High\": \"Triggered if core path blocked and no workaround available.\", \"Low\": \"Explicit 'low' or 'minor', narrow scope (<10 users), workaround exists, no outage/security terms.\", \"Medium\": \"Default; used if none of the above apply.\" }, \"no_workaround_phrases\": [ \"no workaround\", \"no reliable workaround\", \"no consistent workaround\", \"no confirmed workaround\" ] }"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "1cc0ca0a-9423-4fea-b908-848f76041f71",
"name": "AI Case Triage",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
720,
-240
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.0-flash",
"cachedResultName": "models/gemini-2.0-flash"
},
"options": {
"systemMessage": "=You are a Technical Support Triage Engineer for Jira tickets.\n\nInput JSON:\n- ticket: {summary, description, priority, components, labels}\n- domain: {priority_policy, components, guidance_addons, keywords, no_workaround_phrases}\n- constraints: {confidence_threshold}\n\nRules:\n- Base ALL decisions only on ticket.summary + ticket.description.\n- Justifications must QUOTE exact words from the ticket.\n- Update only if confidence \u2265 {{ $json.constraints.confidence_threshold }}; else set action:\"keep\".\n- priority.target MUST be one of domain.priority_policy.\n- component.target MUST be one of domain.components (use \"Other\" if unsure).\n- If evidence is missing (IDs, screenshots, config versions, logs, etc.) add nouns to \"missing_info\".\n- Always return exactly 3 items in \"guidance\":\n - Prefer domain.guidance_addons for the chosen component.\n - If fewer than 3 are relevant, pad with deeper or differential checks (logs, configs, comparisons).\n- Guidance must NOT duplicate missing_info requests.\n\nPriority rules:\n- Highest: explicit outage terms OR systemic API failures (all tenants) OR confirmed security vulnerabilities.\n- High: core path blocked AND no workaround.\n- Low: ALL true \u2192 explicit \"low\"/\"minor\", narrow scope (<10 users), workaround exists, no outage terms.\n- Otherwise: Medium.\n- Never keep Medium if Low criteria are fully met.\n\nConfidence:\n- Assign confidence conservatively (0.5\u20130.85 typical).\n- Only use >0.85 if multiple strong signals (e.g., explicit outage + \"all users\").\n\nSchema:\n{\n \"priority\": {\"action\":\"keep|update\",\"target\":\"High\",\"justification\":\"...\", \"confidence\":0.0},\n \"component\": {\"action\":\"keep|update\",\"target\":\"Backend\",\"justification\":\"...\", \"confidence\":0.0},\n \"missing_info\": [\"...\"],\n \"guidance\": [\"...\", \"...\", \"...\"]\n}\n"
},
"messages": {
"values": [
{
"content": "={{ JSON.stringify($json, null, 2) }}"
}
]
},
"jsonOutput": true
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "d27991d1-54bf-498c-9501-e8736dff3f5e",
"name": "Build JIRA Update",
"type": "n8n-nodes-base.code",
"position": [
16,
304
],
"parameters": {
"jsCode": "// Input: item from \"Normalize LLM Output\"\nconst { ticket, label, threshold, decision, llm_error } = $('Normalize LLM Output').first().json;\n\nconst lbl = label || 'triaged:ai';\n\n// --- Current values ---\nconst origPri = ticket.priority || 'Unknown';\nconst origComp = (Array.isArray(ticket.components) && ticket.components.length) ? ticket.components[0] : 'None';\nconst labelExists = Array.isArray(ticket.labels) && ticket.labels.includes(lbl);\n\n// --- Proposed targets ---\nconst targetPri = decision?.priority?.target;\nconst targetComp = decision?.component?.target;\n\n// --- Changes ---\nlet wantPri = decision.priority?.action === 'update'\n && Number(decision.priority.confidence) >= Number(threshold)\n && targetPri && targetPri !== origPri;\n\nlet wantComp = decision.component?.action === 'update'\n && Number(decision.component.confidence) >= Number(threshold)\n && targetComp && targetComp !== 'Other'\n && targetComp !== origComp;\n\nif (llm_error) { wantPri = false; wantComp = false; }\n\nconst willAddLabel = (!labelExists) && !llm_error;\n\n// --- Build Jira Update payload ---\nconst fields = {};\nif (wantPri) fields.priority = { name: targetPri };\nif (wantComp) fields.components = [{ name: targetComp }];\n\nconst jiraUpdateBody = {};\nif (Object.keys(fields).length) jiraUpdateBody.fields = fields;\nif (willAddLabel) jiraUpdateBody.update = { labels: [{ add: lbl }] };\n\n// --- Build comment body ---\nconst lines = [];\n\n// \u2705 1-line summary\nlet summary = [];\nsummary.push(wantPri ? `Priority \u2192 ${targetPri}` : `Priority kept (${origPri})`);\nsummary.push(wantComp ? `Component \u2192 ${targetComp}` : `Component kept (${origComp})`);\nif (willAddLabel) summary.push(`Label added`);\nlines.push(`*Triage Summary:* ${summary.join(\" | \")}`);\nlines.push(\"\");\n\n// Suggested Next Steps + Missing Info\nif ((decision.guidance && decision.guidance.length) || (decision.missing_info && decision.missing_info.length)) {\n lines.push(\"*Suggested Next Steps*\");\n if (Array.isArray(decision.guidance)) decision.guidance.forEach(g => lines.push(`- ${g}`));\n if (Array.isArray(decision.missing_info) && decision.missing_info.length) {\n lines.push(\"\");\n lines.push(\"*\u26a0\ufe0f Missing Information*\");\n decision.missing_info.forEach(m => lines.push(`- ${m}`));\n }\n lines.push(\"\");\n lines.push(\"----\");\n lines.push(\"\");\n}\n\n// Audit\nlines.push(\"*Audit of Triage*\");\nif (llm_error) {\n lines.push(`\u26a0\ufe0f LLM error (no field updates; label suppressed): ${llm_error}`);\n lines.push(\"\");\n}\nlines.push(`*Issue:* ${ticket.key} (id: ${ticket.id})`);\nlines.push(\"\");\n\n// Priority decision\nlines.push(\"*Priority decision:*\");\nif (wantPri) {\n lines.push(`- Action: update [${origPri} \u2192 ${targetPri}]`);\n} else {\n lines.push(`- Action: keep [${origPri}]`);\n}\nif (decision.priority?.justification) lines.push(`- Justification: \\\"${decision.priority.justification}\\\"`);\nif (decision.priority?.confidence != null) lines.push(`- Confidence: ${Number(decision.priority.confidence).toFixed(2)}`);\nlines.push(`- Verdict: ${wantPri ? \"\u2705 updated\" : \"\u2139\ufe0f kept\"}`);\nlines.push(\"\");\n\n// Component decision\nlines.push(\"*Component decision:*\");\nif (wantComp) {\n lines.push(`- Action: update [${origComp} \u2192 ${targetComp}]`);\n} else {\n lines.push(`- Action: keep [${origComp}]`);\n}\nif (decision.component?.justification) lines.push(`- Justification: \\\"${decision.component.justification}\\\"`);\nif (decision.component?.confidence != null) lines.push(`- Confidence: ${Number(decision.component.confidence).toFixed(2)}`);\nlines.push(`- Verdict: ${wantComp ? \"\u2705 updated\" : \"\u2139\ufe0f kept\"}`);\nlines.push(\"\");\n\n// Label decision\nlines.push(\"*Label decision:*\");\nif (llm_error) {\n lines.push(`- ${lbl}: [absent \u2192 not added due to LLM error]`);\n} else if (willAddLabel) {\n lines.push(`- ${lbl}: [absent \u2192 added]`);\n} else {\n lines.push(`- ${lbl}: [present \u2192 kept]`);\n}\n\nreturn [{\n json: {\n issueKey: ticket.key,\n issueId: ticket.id,\n jiraUpdateBody,\n decision,\n jsmCommentBody: { public: false, body: lines.join(\"\\n\") }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "2969caa9-adb9-4b48-a603-c3f9c1167173",
"name": "JIRA Update",
"type": "n8n-nodes-base.httpRequest",
"position": [
240,
304
],
"parameters": {
"url": "=https://your-domain.atlassian.net/rest/api/3/issue/{{$json.issueKey}}",
"method": "PUT",
"options": {
"response": {
"response": {
"fullResponse": true,
"responseFormat": "json"
}
}
},
"jsonBody": "={{ $json.jiraUpdateBody }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "d2be727b-f73b-4cc2-ae60-bba98317714e",
"name": "Build Payload for LLM",
"type": "n8n-nodes-base.code",
"position": [
496,
-240
],
"parameters": {
"jsCode": "// Support both shapes:\n// - Webhook: { body: { issue: { key, id, fields: {...} } }, ... }\n// - Direct GET: { key, id, fields: {...}, ... }\nconst srcIssue = $json.body?.issue ?? $json;\nconst fields = srcIssue?.fields ?? {};\n\nfunction safeStr(v) { return (v ?? \"\").toString(); }\n\n// Components may be objects or strings\nconst components = Array.isArray(fields.components)\n ? fields.components.map(c => (typeof c === 'string' ? c : c?.name)).filter(Boolean)\n : [];\n\n// Labels from Jira fields\nconst labels = Array.isArray(fields.labels) ? fields.labels : [];\n\n// Priority can be object or string\nconst priorityName = fields.priority?.name ?? (typeof fields.priority === 'string' ? fields.priority : 'Unknown');\n\n// Parse domain (it may arrive as a JSON string)\nlet domainObj = {};\ntry {\n domainObj = typeof $json.domain === 'string' ? JSON.parse($json.domain) : ($json.domain || {});\n} catch {\n domainObj = {};\n}\n\n// Confidence threshold\nconst conf = Number($json.CONF_THRESH);\nconst confidence_threshold = Number.isFinite(conf) ? conf : 0.7;\n\nreturn [{\n json: {\n ticket: {\n key: safeStr(srcIssue?.key),\n id: safeStr(srcIssue?.id),\n summary: safeStr(fields.summary),\n description: safeStr(fields.description),\n priority: safeStr(priorityName),\n components,\n labels,\n },\n domain: domainObj,\n constraints: {\n confidence_threshold,\n },\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "acfa49ef-cb83-437c-ae4d-233f53fbc2fb",
"name": "JIRA Add Comment",
"type": "n8n-nodes-base.httpRequest",
"position": [
688,
304
],
"parameters": {
"url": "=https://your-domain.atlassian.net/rest/servicedeskapi/request/{{$json.issueKey}}/comment",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.jsmCommentBody }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "4465101d-6d93-467a-b1ad-a67a9a4414e3",
"name": "On Update Result",
"type": "n8n-nodes-base.code",
"position": [
464,
304
],
"parameters": {
"jsCode": "// Get the original comment skeleton\nconst base = $items(\"Build JIRA Update\", 0, $itemIndex)?.[0]?.json || {};\n\n// The current item is the HTTP response (because Full Response = true)\nconst status = $json?.statusCode;\nconst text = typeof $json?.body === 'string' ? $json.body : JSON.stringify($json?.body || {});\n\nlet body = base.jsmCommentBody?.body || \"(no audit body)\";\nconst failed = typeof status === 'number' && (status < 200 || status >= 300);\n\nif (failed) {\n body = `\u26a0\ufe0f Jira update failed\\nStatus: ${status}\\nBody: ${text}\\n\\n` + body;\n}\n\n// Output the final comment payload\nreturn [{\n json: {\n issueKey: base.issueKey,\n jsmCommentBody: { public: false, body }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "f960236a-9ef2-454e-9f6f-ad9a73aa0752",
"name": "Metrics Generation",
"type": "n8n-nodes-base.code",
"position": [
912,
304
],
"parameters": {
"jsCode": "// Source A: comment/ctx\nconst ctx = $json;\n\n// Source B: Build JIRA Update (decision, confidence, targets)\nconst updateCtx = $items(\"Build JIRA Update\", 0, $itemIndex)[0]?.json || {};\n\n// Source C: JIRA Update (API response with statusCode)\nconst apiCtx = $items(\"JIRA Update\", 0, $itemIndex)[0]?.json || {};\n\nreturn [{\n json: {\n issueKey: ctx.issueKey || updateCtx.issueKey,\n issueId: ctx.issueId || updateCtx.issueId,\n\n llmError: !!(ctx.llm_error || updateCtx.llm_error),\n\n // Changes applied (from the update payload)\n priorityChange: !!updateCtx.jiraUpdateBody?.fields?.priority,\n componentChange: !!updateCtx.jiraUpdateBody?.fields?.components,\n labelAdded: !!updateCtx.jiraUpdateBody?.update?.labels,\n\n // API statuses (prefer JIRA Update response)\n jiraUpdateStatus: apiCtx.statusCode ?? null,\n jiraCommentStatus: ctx.id ? 201 : null,\n\n // Confidence values (from LLM decision)\n priorityConfidence: updateCtx.decision?.priority?.confidence ?? null,\n componentConfidence: updateCtx.decision?.component?.confidence ?? null,\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "4cdda784-fc9b-4716-b63e-1e0c360e8ce1",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
32,
-240
],
"parameters": {
"path": "dcc89146-5bdb-4330-9bc1-2d021db266cd",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "74ae3c48-51b5-4950-8d36-3bcd97dd7737",
"name": "Normalize LLM Output",
"type": "n8n-nodes-base.code",
"position": [
1072,
-240
],
"parameters": {
"jsCode": "// Pull the original structured payload sent to the LLM\nconst base = $items(\"Build Payload for LLM\", 0, $itemIndex)[0]?.json || {};\nconst setup = $items(\"SET_SETUP\", 0, 0)[0]?.json || {};\n\nconst threshold = Number(base.constraints?.confidence_threshold ?? setup.CONF_THRESH ?? 0.7);\n\nfunction readRawLLMText() {\n return (\n $json?.content?.parts?.[0]?.text ??\n $json?.candidates?.[0]?.content?.parts?.[0]?.text ??\n \"\"\n );\n}\n\nlet llm_error = null;\nlet parsed = null;\n\nconst rawText = readRawLLMText();\n\n// Try to extract a JSON object (with or without code fences)\ntry {\n const match =\n rawText.match(/```json\\s*([\\s\\S]*?)\\s*```/i) ||\n rawText.match(/{[\\s\\S]*}/);\n\n if (!match) throw new Error(\"No JSON-like content in LLM output\");\n\n parsed = JSON.parse(match[1] || match[0]);\n} catch (e) {\n llm_error = `LLM output was not valid JSON: ${e.message}`;\n}\n\n// Current values from the ticket\nconst currentPri = base.ticket?.priority ?? 'Medium';\nconst currentComp = base.ticket?.components?.[0] ?? 'Other';\n\n// Normalize + coerce action when target implies a change\nfunction coerceAction(suggestedAction, target, current) {\n const s = String(suggestedAction || 'keep').toLowerCase();\n if (target && target !== current) return 'update';\n return (s === 'update') ? 'update' : 'keep';\n}\n\n// Normalize (fallbacks if LLM failed)\nconst pri = parsed?.priority ?? {};\nconst comp = parsed?.component ?? {};\n\nconst priorityAction = coerceAction(pri.action, pri.target, currentPri);\nconst componentAction = coerceAction(comp.action, comp.target, currentComp);\n\nreturn [{\n json: {\n ticket: base.ticket || {},\n domain: base.domain,\n threshold,\n label: setup.JIRA_AI_LABEL || 'triaged:ai',\n llm_error, // carry forward\n decision: {\n priority: {\n action: priorityAction,\n target: (pri.target ?? currentPri),\n justification: (pri.justification ?? ''),\n confidence: Number(pri.confidence ?? 0),\n },\n component: {\n action: componentAction,\n target: (comp.target ?? currentComp),\n justification: (comp.justification ?? ''),\n confidence: Number(comp.confidence ?? 0),\n },\n missing_info: Array.isArray(parsed?.missing_info) ? parsed.missing_info : [],\n guidance: Array.isArray(parsed?.guidance) ? parsed.guidance : [],\n }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "bfc707c7-8833-4741-af0c-3a900395af61",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
-576
],
"parameters": {
"color": 5,
"width": 432,
"height": 496,
"content": "## **Entry & Setup** \n\n- Webhook: receives Jira tickets in real time (no polling). \n\n- Setup Parameters: confidence threshold & AI label for triage.\nDomain Knowledge (Trello): keywords, priority rules, troubleshooting guidance.\n\n\n\u27a1\ufe0f Makes the AI act as a *Trello domain expert* instead of giving generic answers."
},
"typeVersion": 1
},
{
"id": "b59d7de1-92c6-42c0-95b8-48db9a3c49b9",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
448,
-576
],
"parameters": {
"color": 2,
"width": 800,
"height": 496,
"content": "## **AI Analysis**\n\nPayload Builder \u2192 AI Case Triage \u2192 Normalize Output.\n\n- Prepares Jira ticket + domain context for Gemini.\n- AI suggests priority, component, missing info, and next steps.\n- Normalization ensures valid JSON + confidence thresholds.\n\n\n\u27a1\ufe0f Guarantees the AI is *consistent, auditable, and safe to automate*."
},
"typeVersion": 1
},
{
"id": "ae0a2993-4e40-4f39-9338-efe01a88660f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
-48
],
"parameters": {
"width": 848,
"height": 496,
"content": "## **Jira Update & Audit**\n\n- Build Jira Update: creates update payload for priority/component/labels.\n- Always generates a comment with:\n \u2022 Suggested Next Steps\n \u2022 Missing Information\n \u2022 Audit trail (decisions, justifications, confidence)\n\n- Jira Update + On Update Result + Add Comment:\n \u2022 Applies field changes\n \u2022 Posts AI-generated comment for engineers\n\n\n\u27a1\ufe0f Provides *transparency and traceability* for every AI decision.\""
},
"typeVersion": 1
},
{
"id": "bafeb986-f0be-4af4-9fdd-01b00f3b89e9",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
864,
-48
],
"parameters": {
"color": 4,
"width": 384,
"height": 496,
"content": "## **Metrics**\n- Captures outcome per ticket:\n \u2022 Priority / Component / Label changes\n \u2022 Confidence scores \n \u2022 API success/failure\n\n\n\u27a1\ufe0f Enables tracking of reliability and continuous improvement."
},
"typeVersion": 1
},
{
"id": "e0dc1407-5f13-4781-b71b-8f556da9f5c8",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
-864
],
"parameters": {
"color": 3,
"width": 1264,
"height": 256,
"content": "# \ud83d\udd39 AI-Driven Jira Ticket Triage\n\nAutomates Jira support tickets with:\n- LLM-powered analysis enriched by Trello domain knowledge. \n- Automatic updates to Priority, Component, and Labels. \n- Transparent audit comments with justifications & confidence. \n\n\n\u27a1\ufe0f Designed as a **scalable prototype**: clear enough to demo, simple enough to extend.\n"
},
"typeVersion": 1
},
{
"id": "8996ae65-f13d-4ff0-855f-fb0c698e926b",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1280,
-864
],
"parameters": {
"color": 6,
"width": 528,
"height": 1312,
"content": "# Domain Schema (Simplified Example)\n\nThis sticky defines the **domain rules** used for AI triage. \nIt is injected via the `SET_SETUP` node as `domain`. \n\n---\n\n## How the Schema Works\n- **components** \u2192 list of valid components for classification \n- **priority_policy** \u2192 allowed severities \n- **priority_rules** \u2192 rules the AI must respect \n- **keywords** \u2192 domain-specific hints for classification \n- **guidance_addons** \u2192 contextual guidance for engineers \n- **no_workaround_phrases** \u2192 escalate severity if found \n\n---\n\n## Example (Simplified)\n\n```json\n{\n \"components\": [\"Boards\", \"Cards\", \"Other\"],\n \"priority_policy\": [\"Highest\", \"High\", \"Medium\", \"Low\"],\n \"priority_rules\": {\n \"Highest\": \"Outage/security keywords detected\",\n \"High\": \"Critical path blocked, no workaround\",\n \"Medium\": \"Default\",\n \"Low\": \"Minor issue or workaround exists\"\n },\n \"keywords\": {\n \"boards\": [\"create board\", \"board not found\"],\n \"cards\": [\"card update failed\"],\n \"outage\": [\"api unavailable\"]\n },\n \"guidance_addons\": {\n \"Boards\": [\n \"Verify API key scopes for board creation\",\n \"Check if org ID is passed correctly\"\n ]\n },\n \"no_workaround_phrases\": [\"no workaround\"]\n}\n```\n\n\ud83d\udca1 **Tip:** Start with this schema, then expand `keywords` and `guidance_addons` for your own product or ticketing system (**Zendesk**, **Freshdesk**, **ServiceNow**).\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "ebbeeff5-3650-4690-a779-304b4a79b6bd",
"connections": {
"Webhook": {
"main": [
[
{
"node": "SET_SETUP",
"type": "main",
"index": 0
}
]
]
},
"SET_SETUP": {
"main": [
[
{
"node": "Build Payload for LLM",
"type": "main",
"index": 0
}
]
]
},
"JIRA Update": {
"main": [
[
{
"node": "On Update Result",
"type": "main",
"index": 0
}
]
]
},
"AI Case Triage": {
"main": [
[
{
"node": "Normalize LLM Output",
"type": "main",
"index": 0
}
]
]
},
"JIRA Add Comment": {
"main": [
[
{
"node": "Metrics Generation",
"type": "main",
"index": 0
}
]
]
},
"On Update Result": {
"main": [
[
{
"node": "JIRA Add Comment",
"type": "main",
"index": 0
}
]
]
},
"Build JIRA Update": {
"main": [
[
{
"node": "JIRA Update",
"type": "main",
"index": 0
}
]
]
},
"Normalize LLM Output": {
"main": [
[
{
"node": "Build JIRA Update",
"type": "main",
"index": 0
}
]
]
},
"Build Payload for LLM": {
"main": [
[
{
"node": "AI Case Triage",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
googlePalmApihttpBasicAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
An extendable triage workflow that classifies severity, sets components, and posts actionable guidance for support engineers using n8n + Gemini + Cache Augmented Generation (CAG). Designed for Jira Service Management, but easily adaptable to Zendesk, Freshdesk, or ServiceNow.
Source: https://n8n.io/workflows/8713/ — 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.
This workflow automates the process of generating stylized product photos for e-commerce by combining real product shots with creative templates. It enables the creation of a complete set of images fo
How it works Runs on schedule (Monday-Friday at 9 AM) to automate lead generation Searches for companies on Google Maps by location and category Extracts owner information from company websites and im
This workflow is designed for creators, designers, and automation builders who need to generate visually consistent images at scale. It is ideal for teams producing branded visuals, social media asset
This workflow creates high-quality, text-rich advertising banners from simple LINE messages.
This workflow turns complex data or topics sent via LINE into beautiful, easy-to-understand Infographics.