{
  "name": "Score and download top YouTube videos to Google Sheets with FetchMedia.io",
  "nodes": [
    {
      "id": "940b8146-9e24-491d-afa3-cc8947287d70",
      "name": "When clicking ‘Execute workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -3536,
        320
      ]
    },
    {
      "id": "cf2e59f7-1a5d-4042-a525-87a87a309ab9",
      "name": "Set Query",
      "type": "n8n-nodes-base.set",
      "position": [
        -3264,
        816
      ]
    },
    {
      "id": "77b7b60e-d8c6-431b-86e8-9ca1fc0bf8f3",
      "name": "Search YouTube",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2704,
        816
      ]
    },
    {
      "id": "1d033bf5-0de6-4462-9b59-58a3ccba1ee0",
      "name": "Filter for Quality",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1344,
        1008
      ]
    },
    {
      "id": "c4fc8a8a-2658-490e-b0da-c3c7ac67fa07",
      "name": "Filter for Relevance",
      "type": "n8n-nodes-base.filter",
      "position": [
        -2256,
        816
      ]
    },
    {
      "id": "6d357a9a-642a-4c5d-9309-e9ffa416ec70",
      "name": "Get Video Metadata",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1760,
        1008
      ]
    },
    {
      "id": "347c1038-c088-4a9a-9e21-d0224482b066",
      "name": "Remove Unnecessary Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -1152,
        1008
      ]
    },
    {
      "id": "96084e37-0f63-4a3a-a831-867c55aa0cfd",
      "name": "Remove Duplicate Videos",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        -1104,
        800
      ]
    },
    {
      "id": "fd0f3127-76ad-4bd9-ae8e-51183858a8bc",
      "name": "Generate Relevance Score",
      "type": "n8n-nodes-base.set",
      "position": [
        -848,
        800
      ]
    },
    {
      "id": "d663a0ea-4b03-40ef-bbbf-82ea08016d22",
      "name": "Send to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -192,
        448
      ]
    },
    {
      "id": "761eda1b-8403-4e22-a1a2-61a1977f13cd",
      "name": "Add Duration Seconds",
      "type": "n8n-nodes-base.code",
      "position": [
        -1552,
        1008
      ]
    },
    {
      "id": "d25bdfd7-16d8-4b49-93e1-98008763613a",
      "name": "Start Fetch (FetchMedia) (HTTP Request)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        16,
        448
      ]
    },
    {
      "id": "34ae2f83-aa8a-4359-bc72-c1b992d9fb3c",
      "name": "Init Polling State (Set)",
      "type": "n8n-nodes-base.set",
      "position": [
        224,
        448
      ]
    },
    {
      "id": "839f4997-97c0-4d0d-b500-18d56ebb9e25",
      "name": "Poll loop (Wait → GET status → check → loop)",
      "type": "n8n-nodes-base.wait",
      "position": [
        448,
        448
      ]
    },
    {
      "id": "3541fdc9-a262-47d1-bc69-8e364a2ad58b",
      "name": "Check Fetch Status (HTTP Request)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        656,
        448
      ]
    },
    {
      "id": "e3bc6de3-75bd-4c5c-aef8-0c6edf7e6c96",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2976,
        448
      ],
      "parameters": {
        "width": 736,
        "height": 304,
        "content": "### YouTube Discovery & Initial Filtering\n\n\nThis section searches YouTube using the YouTube Data API\nand applies initial relevance and quality filters.\n\nVideos are filtered by:\n- Title keywords\n- Publ"
      }
    },
    {
      "id": "386d1600-7d71-4b22-94a4-6ba524da2d54",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1648,
        1232
      ],
      "parameters": {
        "width": 480,
        "height": 272,
        "content": "### Duration & Quality Validation\n\nYouTube returns video duration in ISO-8601 format (PT#M#S).\n\nThis step:\n- Converts duration to seconds\n- Enables numeric filtering (e.g. Shorts vs long-form)\n- Ensur"
      }
    },
    {
      "id": "fbd55eda-7596-409f-ab0b-cf52bba5614c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1024,
        480
      ],
      "parameters": {
        "width": 608,
        "height": 240,
        "content": "### Deduplication & Relevance Scoring\n\n\nThis section removes duplicate videos using videoId\nand assigns a relevance score based on engagement\nand metadata signals.\n\nVideos are then sorted by score and"
      }
    },
    {
      "id": "13881be5-3f6d-478e-b686-3aa60da933f6",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -224
      ],
      "parameters": {
        "width": 1088,
        "height": 400,
        "content": "## Asynchronous Download & Status Polling (FetchMedia)\n\nSelected videos are submitted to FetchMedia.io for download.\n\nBecause downloads are asynchronous:\n- The workflow polls the API at intervals\n- St"
      }
    },
    {
      "id": "647e12dc-66a6-49a8-9def-c6126a5ed423",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1968,
        560
      ],
      "parameters": {
        "width": 336,
        "height": 240,
        "content": "## Batch Processing Note\n\nThe Done branch of this loop is intentionally unused.\n\nAll processing occurs inside the batch loop\nto preserve item context and enable controlled retries."
      }
    },
    {
      "id": "e9d70b17-aabc-44a1-8075-313563ceeed5",
      "name": "Loop over YouTube Results",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -2048,
        816
      ]
    },
    {
      "id": "86d6ef5c-81f2-4f97-9cac-a66ff0c83049",
      "name": "Split search queries",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2992,
        816
      ]
    },
    {
      "id": "c2dd49f2-7507-4d87-a2b2-bc93f99c9f87",
      "name": "Split YouTube Results",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2464,
        816
      ]
    },
    {
      "id": "da59ced5-3853-49b8-9d27-836c2e2a0c6d",
      "name": "Take only the amount of top videos",
      "type": "n8n-nodes-base.limit",
      "position": [
        -416,
        800
      ]
    },
    {
      "id": "e45fddc2-32ea-4806-a13a-1bce27b89da7",
      "name": "Sort by Relevance (Our score)",
      "type": "n8n-nodes-base.sort",
      "position": [
        -624,
        800
      ]
    },
    {
      "id": "1a1664ba-0c66-4bcf-af3a-e87daaefdbe4",
      "name": "Check max attempts",
      "type": "n8n-nodes-base.if",
      "position": [
        480,
        912
      ]
    },
    {
      "id": "03e48bda-9ca9-48f8-9884-b11966b75319",
      "name": "Increase attempt counter",
      "type": "n8n-nodes-base.code",
      "position": [
        1184,
        1056
      ]
    },
    {
      "id": "0c7a3b89-3ea0-4430-a7d6-b32f16555864",
      "name": "Search for the relevant video",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        688,
        912
      ]
    },
    {
      "id": "53226a86-cb8a-492a-9903-4a5c135c3c32",
      "name": "Update the status of the download",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        896,
        912
      ]
    },
    {
      "id": "8f76971a-92f0-42fd-828a-2507c8000df0",
      "name": "Search for the relevant video1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1056,
        432
      ]
    },
    {
      "id": "da150c72-b361-4189-8ef4-45dea4ced79d",
      "name": "Update the status and the download URL",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1232,
        432
      ]
    },
    {
      "id": "6e4fca81-19fc-4e96-9c90-c30a02cf747c",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -432
      ],
      "parameters": {
        "width": 800,
        "height": 608,
        "content": "## How it works\n\nThis workflow searches YouTube for educational videos using a predefined list of queries. It filters out low-quality or irrelevant results and enriches the remaining videos with addit"
      }
    },
    {
      "id": "e37e00be-3c8d-4396-951a-bf6855120ad7",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3312,
        768
      ],
      "parameters": {
        "width": 992,
        "height": 192,
        "content": "### Search YouTube + split results"
      }
    },
    {
      "id": "79b62512-61b7-404e-bf60-a1240ef3cf54",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2304,
        768
      ],
      "parameters": {
        "width": null,
        "height": 192,
        "content": "### Filter for relevance (title keywords / exclude spam)"
      }
    },
    {
      "id": "0e827385-25d0-4159-bd26-aa245d7ab6b8",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1872,
        960
      ],
      "parameters": {
        "width": 480,
        "height": 208,
        "content": "### Fetch video metadata + convert duration to seconds"
      }
    },
    {
      "id": "1d1fb890-746f-4cae-b3fe-545dc523ca40",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1376,
        960
      ],
      "parameters": {
        "width": 400,
        "height": 208,
        "content": "### Quality filter (views/likes/date/duration)"
      }
    },
    {
      "id": "eb1fcafe-8efd-4ab1-b39d-632f6a04f961",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -928,
        752
      ],
      "parameters": {
        "width": 704,
        "height": 224,
        "content": "### Score + sort + limit top results"
      }
    },
    {
      "id": "9bed9cdd-44be-4cbd-94e6-3e1df2e7181f",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        368
      ],
      "parameters": {
        "width": 1712,
        "height": 256,
        "content": "### Write to Sheets + download + poll status + update row"
      }
    },
    {
      "id": "e9d2c28a-4bff-49b2-9e8b-fecdf6e70cf3",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        800
      ],
      "parameters": {
        "width": 1216,
        "height": 464,
        "content": "### If the FetchMedia service is unresponsive, we limit the number of retry attempts to 30.\nAfter reaching this threshold, the process will stop and the corresponding row in the Google Sheet will be u"
      }
    },
    {
      "id": "627cf3d4-2e18-4a95-b3fc-14dd8a78e039",
      "name": "Check if download is complete",
      "type": "n8n-nodes-base.if",
      "position": [
        848,
        448
      ]
    }
  ],
  "connections": {
    "Set Query": {
      "main": [
        [
          {
            "node": "Split search queries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search YouTube": {
      "main": [
        [
          {
            "node": "Split YouTube Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check max attempts": {
      "main": [
        [
          {
            "node": "Poll loop (Wait → GET status → check → loop)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Search for the relevant video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter for Quality": {
      "main": [
        [
          {
            "node": "Remove Unnecessary Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Video Metadata": {
      "main": [
        [
          {
            "node": "Add Duration Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Duration Seconds": {
      "main": [
        [
          {
            "node": "Filter for Quality",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter for Relevance": {
      "main": [
        [
          {
            "node": "Loop over YouTube Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split search queries": {
      "main": [
        [
          {
            "node": "Search YouTube",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Google Sheets": {
      "main": [
        [
          {
            "node": "Start Fetch (FetchMedia) (HTTP Request)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split YouTube Results": {
      "main": [
        [
          {
            "node": "Filter for Relevance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Videos": {
      "main": [
        [
          {
            "node": "Generate Relevance Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Relevance Score": {
      "main": [
        [
          {
            "node": "Sort by Relevance (Our score)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increase attempt counter": {
      "main": [
        [
          {
            "node": "Check max attempts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Init Polling State (Set)": {
      "main": [
        [
          {
            "node": "Poll loop (Wait → GET status → check → loop)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop over YouTube Results": {
      "main": [
        [
          {
            "node": "Remove Duplicate Videos",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Video Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Unnecessary Fields": {
      "main": [
        [
          {
            "node": "Loop over YouTube Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if download is complete": {
      "main": [
        [
          {
            "node": "Search for the relevant video1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Increase attempt counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search for the relevant video": {
      "main": [
        [
          {
            "node": "Update the status of the download",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by Relevance (Our score)": {
      "main": [
        [
          {
            "node": "Take only the amount of top videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search for the relevant video1": {
      "main": [
        [
          {
            "node": "Update the status and the download URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Fetch Status (HTTP Request)": {
      "main": [
        [
          {
            "node": "Check if download is complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Take only the amount of top videos": {
      "main": [
        [
          {
            "node": "Send to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Set Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Fetch (FetchMedia) (HTTP Request)": {
      "main": [
        [
          {
            "node": "Init Polling State (Set)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll loop (Wait → GET status → check → loop)": {
      "main": [
        [
          {
            "node": "Check Fetch Status (HTTP Request)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}