{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "e50e2e16-32d9-4ce4-baea-ba25970bf3db",
      "name": "Webhook - Incoming Ticket",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1440,
        576
      ],
      "parameters": {
        "path": "route-ticket",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "172223ae-9832-47b1-9731-b0ca55123e32",
      "name": "Normalize Ticket",
      "type": "n8n-nodes-base.code",
      "position": [
        -1216,
        576
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.body;\nreturn {\n  json: {\n    ticketId: raw.ticketId || 'TKT-' + Date.now(),\n    subject: (raw.subject || '').trim(),\n    body: (raw.body || raw.message || '').trim().substring(0, 2000),\n    email: (raw.email || '').toLowerCase().trim(),\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8a1ff0c3-f97c-442e-a762-7aa4177ed961",
      "name": "AI - Classify",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -992,
        576
      ],
      "parameters": {
        "text": "=Classify this support ticket.\n\nSubject: {{ $json.subject }}\nBody: {{ $json.body }}\n\nReturn ONLY valid JSON:\n{\"category\": \"billing|technical|sales|general\", \"urgency\": \"low|normal|urgent\", \"confidence\": 0.0 to 1.0}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "262ba98c-e0cc-4c55-9abb-f92522260b5b",
      "name": "Parse + Validate",
      "type": "n8n-nodes-base.code",
      "position": [
        -640,
        576
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json;\nlet parsed;\ntry { const text = typeof raw.output === 'string' ? raw.output : JSON.stringify(raw.output); const jsonMatch = text.match(/\\{[\\s\\S]*\\}/); parsed = JSON.parse(jsonMatch ? jsonMatch[0] : text); } catch(e) { parsed = { category: 'general', urgency: 'normal', confidence: 0 }; }\nconst validCats = ['billing','technical','sales','general'];\nif (!validCats.includes(parsed.category)) parsed.category = 'general';\nif (typeof parsed.confidence !== 'number') parsed.confidence = 0;\nreturn { json: { ...parsed, ticketId: $('Normalize Ticket').first().json.ticketId, email: $('Normalize Ticket').first().json.email } };"
      },
      "typeVersion": 2
    },
    {
      "id": "0a29b0e6-9759-43d4-bb97-822621b0ccd2",
      "name": "Confidence Threshold",
      "type": "n8n-nodes-base.switch",
      "position": [
        -416,
        560
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "High Confidence",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c1",
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    },
                    "leftValue": "={{ $json.confidence }}",
                    "rightValue": 0.85
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Medium Confidence",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c2",
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    },
                    "leftValue": "={{ $json.confidence }}",
                    "rightValue": 0.6
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Low Confidence",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c3",
                    "operator": {
                      "type": "number",
                      "operation": "lt"
                    },
                    "leftValue": "={{ $json.confidence }}",
                    "rightValue": 0.6
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "ffb130a2-0ba6-43f2-be3c-7b3ee667a719",
      "name": "Route by Category",
      "type": "n8n-nodes-base.switch",
      "position": [
        -192,
        176
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Billing",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c4",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "billing"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Technical",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c5",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "technical"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Sales",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c6",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "sales"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "62b71a99-910d-4d1e-a8b6-a3298221688d",
      "name": "Flag for Review",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        576
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, action: 'flagged_for_review' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "3671dfc2-9862-4d96-9683-c97a9e32d88e",
      "name": "Send to Human Queue",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        768
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, action: 'sent_to_human' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "03766d0e-16c7-42a1-ab66-a0b6753fd93e",
      "name": "Billing Team",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        0
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, routed: 'billing_team' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "4ae34c34-7f16-4d2e-b2af-feeae04fbf5f",
      "name": "Technical Team",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        192
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, routed: 'technical_team' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "11a66db0-a365-408c-953f-2154675cbf50",
      "name": "Sales Team",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        384
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, routed: 'sales_team' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "e97e0dc7-340c-407e-b3e5-6ea74297e76a",
      "name": "General Inbox",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        960
      ],
      "parameters": {
        "jsCode": "return { json: { ...$input.first().json, routed: 'general_inbox' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "74e4b921-5177-4618-bd89-e6ee95408080",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        256,
        480
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ticketId: $json.ticketId, routed: $json.routed || $json.action, category: $json.category, confidence: $json.confidence }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "dfe71ff5-1259-414a-9106-cc260b403556",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -928,
        800
      ],
      "parameters": {
        "model": "google/gemini-3-flash-preview",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1fb7d84f-5cc8-475b-8fbe-772b35a0183c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2128,
        128
      ],
      "parameters": {
        "width": 576,
        "height": 864,
        "content": "## Classification-then-Routing\n\n### How it works\n1. **Webhook** receives an incoming support ticket.\n2. **Normalize Ticket** (Code node) cleans and standardizes the input.\n3. **AI - Classify** (AI Agent + OpenRouter) classifies the ticket by category and assigns a confidence score.\n4. **Parse + Validate** (Code node) checks that the AI output has valid categories and scores.\n5. **Confidence Threshold** (Switch node) routes by confidence level: high (>0.85) processes autonomously, medium (0.6-0.85) gets flagged for review, and low (<0.6) goes to a human queue.\n6. **Route by Category** (Switch node) fans high-confidence tickets to Billing, Technical, Sales, or General team handlers.\n\n### Setup\n- Connect your **LLM credentials** (e.g., OpenRouter, OpenAI) to the Chat Model node\n- Copy the Webhook test URL and send test tickets with varying complexity to see different confidence routing\n- Adjust confidence thresholds in the **Confidence Threshold** Switch node to match your risk tolerance\n\n### Customization\n- Replace the team handler Code nodes with real integrations (Slack, Jira, email)\n- Adjust thresholds over time as you validate AI accuracy: lower them to automate more\n\n\nThis template is a learning companion to the Production AI Playbook, a  series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n.  \n\n\nThis template is a learning companion to the Production AI Playbook, a series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n.  \n\nhttps://go.n8n.io/PAP-D&A-Blog"
      },
      "typeVersion": 1
    },
    {
      "id": "18171911-4cd5-4971-9a86-c861475ea933",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1504,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 704,
        "content": "## Receive & Normalize"
      },
      "typeVersion": 1
    },
    {
      "id": "ce73ad96-e96b-416c-9bab-9164af4bbc7a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1024,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 704,
        "content": "## AI Classification"
      },
      "typeVersion": 1
    },
    {
      "id": "fd237475-e0b1-43d7-acd2-eeff54c72076",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 704,
        "content": "## Confidence Routing"
      },
      "typeVersion": 1
    },
    {
      "id": "b2a3d9d5-9f58-44c3-a1cc-864f36c58d0a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 1184,
        "content": "## Category Routing"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Sales Team": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Billing Team": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Classify": {
      "main": [
        [
          {
            "node": "Parse + Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "General Inbox": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Technical Team": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag for Review": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Ticket": {
      "main": [
        [
          {
            "node": "AI - Classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse + Validate": {
      "main": [
        [
          {
            "node": "Confidence Threshold",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Category": {
      "main": [
        [
          {
            "node": "Billing Team",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Technical Team",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sales Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Human Queue": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confidence Threshold": {
      "main": [
        [
          {
            "node": "Route by Category",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag for Review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send to Human Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Classify",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Incoming Ticket": {
      "main": [
        [
          {
            "node": "Normalize Ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}