{
  "name": "Nodbot - OpenRouter AI fallback with JSON validation",
  "nodes": [
    {
      "parameters": {
        "path": "integration-openrouter-n8n",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "id": "e765aed5-a07f-43c3-9a50-2a37166e7bb3",
      "name": "Webhook AI task",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst text = String(src.text ?? '').trim();\nif (text.length < 20) throw new Error('AI task text is too short');\nconst allowed = Array.isArray(src.allowed_labels) ? src.allowed_labels : ['other'];\nconst schemaHint = {\n  label: `one of: ${allowed.join(', ')}`,\n  confidence: 'number from 0 to 1',\n  summary: 'short Russian summary',\n  next_action: 'draft_reply | create_task | human_review'\n};\nreturn [{ json: {\n  task_type: src.task_type ?? 'classification',\n  ticket_id: src.ticket_id ?? '',\n  models: ['openai/gpt-4.1-mini', 'anthropic/claude-3.5-haiku', 'google/gemini-flash-1.5'],\n  max_tokens: 700,\n  temperature: 0.1,\n  messages: [\n    { role: 'system', content: `Return only valid JSON. Schema: ${JSON.stringify(schemaHint)}. Allowed labels: ${allowed.join(', ')}` },\n    { role: 'user', content: text.slice(0, 6000) }\n  ],\n  validation: { allowed_labels: allowed, min_confidence: 0.78, fallback_action: 'human_review' }\n}}];"
      },
      "id": "0d5fcab0-1e82-4417-9536-85bc4e31ef18",
      "name": "Prepare prompt contract",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst text = String(src.text ?? '').trim();\nif (text.length < 20) throw new Error('AI task text is too short');\nconst allowed = Array.isArray(src.allowed_labels) ? src.allowed_labels : ['other'];\nconst schemaHint = {\n  label: `one of: ${allowed.join(', ')}`,\n  confidence: 'number from 0 to 1',\n  summary: 'short Russian summary',\n  next_action: 'draft_reply | create_task | human_review'\n};\nreturn [{ json: {\n  task_type: src.task_type ?? 'classification',\n  ticket_id: src.ticket_id ?? '',\n  models: ['openai/gpt-4.1-mini', 'anthropic/claude-3.5-haiku', 'google/gemini-flash-1.5'],\n  max_tokens: 700,\n  temperature: 0.1,\n  messages: [\n    { role: 'system', content: `Return only valid JSON. Schema: ${JSON.stringify(schemaHint)}. Allowed labels: ${allowed.join(', ')}` },\n    { role: 'user', content: text.slice(0, 6000) }\n  ],\n  validation: { allowed_labels: allowed, min_confidence: 0.78, fallback_action: 'human_review' }\n}}];"
      },
      "id": "cdb1a7f4-1fce-4559-813c-db02573da6f7",
      "name": "Call OpenRouter",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst text = String(src.text ?? '').trim();\nif (text.length < 20) throw new Error('AI task text is too short');\nconst allowed = Array.isArray(src.allowed_labels) ? src.allowed_labels : ['other'];\nconst schemaHint = {\n  label: `one of: ${allowed.join(', ')}`,\n  confidence: 'number from 0 to 1',\n  summary: 'short Russian summary',\n  next_action: 'draft_reply | create_task | human_review'\n};\nreturn [{ json: {\n  task_type: src.task_type ?? 'classification',\n  ticket_id: src.ticket_id ?? '',\n  models: ['openai/gpt-4.1-mini', 'anthropic/claude-3.5-haiku', 'google/gemini-flash-1.5'],\n  max_tokens: 700,\n  temperature: 0.1,\n  messages: [\n    { role: 'system', content: `Return only valid JSON. Schema: ${JSON.stringify(schemaHint)}. Allowed labels: ${allowed.join(', ')}` },\n    { role: 'user', content: text.slice(0, 6000) }\n  ],\n  validation: { allowed_labels: allowed, min_confidence: 0.78, fallback_action: 'human_review' }\n}}];"
      },
      "id": "9b60a4cd-dc77-43b9-9af4-9539e889a476",
      "name": "Validate JSON output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst text = String(src.text ?? '').trim();\nif (text.length < 20) throw new Error('AI task text is too short');\nconst allowed = Array.isArray(src.allowed_labels) ? src.allowed_labels : ['other'];\nconst schemaHint = {\n  label: `one of: ${allowed.join(', ')}`,\n  confidence: 'number from 0 to 1',\n  summary: 'short Russian summary',\n  next_action: 'draft_reply | create_task | human_review'\n};\nreturn [{ json: {\n  task_type: src.task_type ?? 'classification',\n  ticket_id: src.ticket_id ?? '',\n  models: ['openai/gpt-4.1-mini', 'anthropic/claude-3.5-haiku', 'google/gemini-flash-1.5'],\n  max_tokens: 700,\n  temperature: 0.1,\n  messages: [\n    { role: 'system', content: `Return only valid JSON. Schema: ${JSON.stringify(schemaHint)}. Allowed labels: ${allowed.join(', ')}` },\n    { role: 'user', content: text.slice(0, 6000) }\n  ],\n  validation: { allowed_labels: allowed, min_confidence: 0.78, fallback_action: 'human_review' }\n}}];"
      },
      "id": "a9258689-14f0-4c99-8994-da6441960ef0",
      "name": "Route by confidence",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {},
      "id": "71b34916-cf46-4e8c-9186-220c0771cafe",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1400,
        260
      ]
    }
  ],
  "connections": {
    "Webhook AI task": {
      "main": [
        [
          {
            "node": "Prepare prompt contract",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare prompt contract": {
      "main": [
        [
          {
            "node": "Call OpenRouter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call OpenRouter": {
      "main": [
        [
          {
            "node": "Validate JSON output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate JSON output": {
      "main": [
        [
          {
            "node": "Route by confidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by confidence": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "integration",
    "production"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "b09b84e3-6aaf-40e6-934e-15fe0b5d05f2",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}