{
  "name": "Fall back to a second AI provider on 429 and 529 errors with HTTP Request",
  "nodes": [
    {
      "parameters": {
        "content": "## Fall back to a second AI provider when the first one fails\n\nKeeps an AI workflow answering when your primary provider rate-limits, overloads or times out.\n\n### Who's it for\nAnyone running an AI call in production where a failed request means a failed run: support triage, classification, enrichment, anything a person is waiting on.\n\n### How it works\n- **Define the ladder** holds both endpoints and both models in one place, so nothing is buried inside a node.\n- **Try the primary** calls the first provider. It retries twice on its own, so a one-second blip never reaches the fallback.\n- **Primary answered?** treats anything outside 2xx as a failure. That covers 429 rate limits, 529 overloads, 5xx outages and the empty body a timeout returns.\n- On failure, **Try the fallback** calls a different provider using its own credential.\n- **Report the outcome** returns `servedBy`, so you can see which model actually answered.\n\n### Setup\n1. Open **Define the ladder** and set both endpoints and models. Point them at two different providers. A fallback on the same provider will not help you when that provider is the outage.\n2. Attach a credential to **Try the primary** and to **Try the fallback** under Authentication, Generic, Header Auth. They need different credentials, which is the whole point.\n3. Replace **Start** with whatever really triggers this: a webhook, a queue, a schedule.\n\n### Requirements\nAPI credentials for two AI providers. No community nodes.\n\n### How to customize\nFor a third rung, copy the **Try the fallback** and **Fallback answered?** pair and point the false branch at it. Log `servedBy`: if the fallback starts serving a real share of traffic, your primary is quietly degrading and you will see it here weeks early.\n\nFull walkthrough: https://www.theagentecosystem.com/blog/n8n-multi-model-fallback",
        "height": 1058,
        "width": 760,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -688
      ],
      "name": "Note: Overview"
    },
    {
      "parameters": {
        "content": "## 1. Configure both providers\nEndpoints, models and credentials live here. Two different vendors, on purpose.",
        "height": 260,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        490
      ],
      "name": "Note: Configure both providers"
    },
    {
      "parameters": {
        "content": "## 2. Try the primary\nRetries twice on its own, then treats any non-2xx as a failure.",
        "height": 260,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        490
      ],
      "name": "Note: Try the primary"
    },
    {
      "parameters": {
        "content": "## 3. Drop to the fallback\nA different provider with its own credential. Only reached when the primary is genuinely down.",
        "height": 260,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        920,
        650
      ],
      "name": "Note: Drop to the fallback"
    },
    {
      "parameters": {
        "content": "## 4. Report which model answered\nReturns servedBy so you can measure how often the fallback is carrying you.",
        "height": 540,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1400,
        490
      ],
      "name": "Note: Report which model answered"
    },
    {
      "parameters": {},
      "id": "c1d2e3f4-0000-4000-8000-000000000003",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        640
      ]
    },
    {
      "parameters": {
        "jsCode": "// The ladder. Primary first, fallback second, and deliberately on a DIFFERENT\n// provider — a fallback on the same provider does not help you when that\n// provider is the thing having the outage.\nreturn [{\n  json: {\n    prompt: 'In one sentence: what should a workflow do when its model provider returns 529?',\n    primary: {\n      name: 'openai/gpt-4o-mini',\n      endpoint: 'https://api.openai.com/v1/chat/completions',\n      model: 'gpt-4o-mini',\n    },\n    fallback: {\n      name: 'anthropic/claude-haiku-4-5',\n      endpoint: 'https://api.anthropic.com/v1/messages',\n      model: 'claude-haiku-4-5-20251001',\n    },\n  },\n}];"
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000004",
      "name": "Define the ladder",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        640
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.primary.endpoint }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $json.primary.model, max_tokens: 300, messages: [{ role: 'user', content: $json.prompt }] }) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000005",
      "name": "Try the primary",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        480,
        640
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1d2e3f4-0000-4000-8000-000000000011",
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 300,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000006",
      "name": "Primary answered?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        640
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Define the ladder').first().json.fallback.endpoint }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $('Define the ladder').first().json.fallback.model, max_tokens: 300, messages: [{ role: 'user', content: $('Define the ladder').first().json.prompt }] }) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000007",
      "name": "Try the fallback",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        800
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1d2e3f4-0000-4000-8000-000000000012",
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 300,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000008",
      "name": "Fallback answered?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1200,
        800
      ]
    },
    {
      "parameters": {
        "jsCode": "// Both rungs are gone. Return a clean, explicit failure rather than throwing:\n// whatever called this workflow can then decide to queue the work, degrade the\n// feature, or tell the user — all of which beat an exception with no context.\nconst ladder = $('Define the ladder').first().json;\n\nreturn [{\n  json: {\n    servedBy: 'none',\n    ok: false,\n    answer: null,\n    primaryStatus: $('Try the primary').first().json.statusCode ?? null,\n    fallbackStatus: $('Try the fallback').first().json.statusCode ?? null,\n    tried: [ladder.primary.name, ladder.fallback.name],\n  },\n}];"
      },
      "id": "c1d2e3f4-0000-4000-8000-000000000009",
      "name": "Both rungs failed",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        920
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalise whichever provider answered into one shape, so everything\n// downstream stops caring which one it was.\n//\n// servedBy is the number worth logging. If the fallback starts serving a\n// noticeable share of traffic, that is your primary quietly degrading — and you\n// will see it here weeks before you would notice it anywhere else.\nconst ladder = $('Define the ladder').first().json;\nconst res = $input.first().json;\nconst body = res.body ?? {};\n\n// OpenAI-shaped and Anthropic-shaped replies land in different places.\nconst answer =\n  body?.choices?.[0]?.message?.content ??\n  body?.content?.[0]?.text ??\n  null;\n\nconst usedFallback = $('Primary answered?').isExecuted\n  ? ($('Try the primary').first().json.statusCode ?? 599) >= 300\n  : false;\n\nreturn [{\n  json: {\n    servedBy: usedFallback ? 'fallback' : 'primary',\n    model: usedFallback ? ladder.fallback.name : ladder.primary.name,\n    ok: true,\n    answer,\n    statusCode: res.statusCode ?? null,\n  },\n}];"
      },
      "id": "c1d2e3f4-0000-4000-8000-00000000000a",
      "name": "Report the outcome",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        640
      ]
    },
    {
      "parameters": {},
      "id": "c1d2e3f4-0000-4000-8000-00000000000b",
      "name": "Finished",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1680,
        760
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Define the ladder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define the ladder": {
      "main": [
        [
          {
            "node": "Try the primary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try the primary": {
      "main": [
        [
          {
            "node": "Primary answered?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Primary answered?": {
      "main": [
        [
          {
            "node": "Report the outcome",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try the fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try the fallback": {
      "main": [
        [
          {
            "node": "Fallback answered?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fallback answered?": {
      "main": [
        [
          {
            "node": "Report the outcome",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Both rungs failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Report the outcome": {
      "main": [
        [
          {
            "node": "Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Both rungs failed": {
      "main": [
        [
          {
            "node": "Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "tags": []
}
