{
  "name": "1 \u00b7 sky-harbor-Failover Gateway",
  "nodes": [
    {
      "parameters": {
        "content": "## Failover Gateway\nTries the CLOUD model first. If the cloud call fails (offline, rate-limited, key revoked \u2014 the Fable scenario), it automatically falls back to your LOCAL Ollama model so you never go dark.\n\n**Setup**\n1. Create a Header Auth credential named `x-api-key` with your Anthropic key on the 'Cloud (Primary)' node.\n2. Edit the 'Settings' node: set cloudModel, localModel, ollamaBaseUrl.\n3. Docker users: ollamaBaseUrl = http://host.docker.internal:11434\n\n**Test**\ncurl -X POST <webhook-url> -H 'Content-Type: application/json' -d '{\"prompt\":\"Explain a haiku in one line.\"}'",
        "height": 320,
        "width": 420
      },
      "id": "note1",
      "name": "Readme",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -460,
        80
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "failover-gateway",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "wf1n1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -440,
        460
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "prompt",
              "type": "string",
              "value": "={{ $json.body.prompt || $json.prompt }}"
            },
            {
              "id": "a2",
              "name": "cloudModel",
              "type": "string",
              "value": "claude-sonnet-4-6"
            },
            {
              "id": "a3",
              "name": "localModel",
              "type": "string",
              "value": "llama3.1"
            },
            {
              "id": "a4",
              "name": "ollamaBaseUrl",
              "type": "string",
              "value": "http://localhost:11434"
            },
            {
              "id": "a5",
              "name": "systemPrompt",
              "type": "string",
              "value": "You are a concise, helpful assistant."
            },
            {
              "id": "a6",
              "name": "maxTokens",
              "type": "number",
              "value": 1024
            }
          ]
        },
        "options": {}
      },
      "id": "wf1n2",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -220,
        460
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $json.cloudModel, max_tokens: $json.maxTokens, system: $json.systemPrompt, messages: [ { role: 'user', content: $json.prompt } ] }) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "wf1n3",
      "name": "Cloud (Primary)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        20,
        460
      ],
      "onError": "continueErrorOutput",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const s = $('Settings').first().json;\nconst r = $input.first().json;\nconst text = (r && r.content && r.content[0] && r.content[0].text) ? r.content[0].text : '';\nreturn [{ json: { answer: text, answeredBy: 'cloud', model: s.cloudModel, prompt: s.prompt } }];"
      },
      "id": "wf1n4",
      "name": "Format - Cloud",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        360
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Settings').first().json.ollamaBaseUrl }}/api/chat",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $('Settings').first().json.localModel, messages: [ { role: 'user', content: $('Settings').first().json.prompt } ], stream: false }) }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "wf1n5",
      "name": "Local (Fallback)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        260,
        560
      ]
    },
    {
      "parameters": {
        "jsCode": "const s = $('Settings').first().json;\nconst r = $input.first().json;\nconst text = (r && r.message && r.message.content) ? r.message.content : (r && r.response ? r.response : '');\nreturn [{ json: { answer: text, answeredBy: 'local (cloud failover)', model: s.localModel, prompt: s.prompt } }];"
      },
      "id": "wf1n6",
      "name": "Format - Local",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        560
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {}
      },
      "id": "wf1n7",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        760,
        460
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Cloud (Primary)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cloud (Primary)": {
      "main": [
        [
          {
            "node": "Format - Cloud",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Local (Fallback)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format - Cloud": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Local (Fallback)": {
      "main": [
        [
          {
            "node": "Format - Local",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format - Local": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  }
}