{
  "name": "Faceless YouTube Script Factory (OutlierKit → AI → Google Sheets)",
  "nodes": [
    {
      "parameters": {
        "content": "## 🎬 Faceless YouTube Script Factory\n\nFinds proven outlier videos in your niche with the **OutlierKit API**, pulls each video's transcript, has AI draft a fully original script targeting the same demand, and saves everything to a **Google Sheet**.\n\n### Setup (5 minutes)\n1. **OutlierKit API key** — Pro or Max plan → dashboard → Settings → API. Docs: https://outlierkit.com/app/api-docs\n2. On both HTTP Request nodes, create one **Header Auth** credential:\n   - Name: `Authorization`\n   - Value: `Bearer YOUR_OUTLIERKIT_API_KEY`\n3. Connect your **OpenAI** credential on the *Draft Script with AI* node (or swap in Claude/Gemini).\n4. Create a Google Sheet with headers:\n   `Date | Niche | Video Title | Channel | Views | Outlier Score | Video URL | Script Draft`\n   and select it in the *Save to Google Sheets* node.\n5. Edit **Niche Settings** with your niche, then hit *Execute workflow*.\n\n💳 Cost per run: 1 + `videosPerRun` OutlierKit credits (default = 6).\n\nGuide: https://outlierkit.com/resources/n8n-faceless-youtube-template/",
        "height": 620,
        "width": 420
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000001",
      "name": "Sticky Note — Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-880, 160]
    },
    {
      "parameters": {
        "content": "### 1️⃣ Find proven demand\n\n`POST /api/v1/outliers/search` returns videos that are massively over-performing their channel's average — proven demand, not guesses. Tune `minOutlierScore` in Niche Settings (3 = 3× the channel average).",
        "height": 260,
        "width": 400,
        "color": 5
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000002",
      "name": "Sticky Note — Find",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-160, 160]
    },
    {
      "parameters": {
        "content": "### 2️⃣ Pull the transcript\n\n`GET /api/v1/videos/:id/transcript` returns the cached transcript with timed segments — no scraping, no YouTube API quota.",
        "height": 260,
        "width": 360,
        "color": 5
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000003",
      "name": "Sticky Note — Transcript",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [300, 160]
    },
    {
      "parameters": {
        "content": "### 3️⃣ Draft + save\n\nThe AI writes a fully original script (titles, thumbnail concept, hook, sections, re-hooks) from the outlier's transcript. Each draft is appended as one row in your Google Sheet — your content pipeline for the week.",
        "height": 260,
        "width": 400,
        "color": 5
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000004",
      "name": "Sticky Note — Draft",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [720, 160]
    },
    {
      "parameters": {},
      "id": "f1a7c2e0-1111-4a01-9c01-000000000010",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [-660, 620]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [1],
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000011",
      "name": "Every Monday 7 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [-660, 460]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "niche-assignment",
              "name": "niche",
              "value": "space facts",
              "type": "string"
            },
            {
              "id": "score-assignment",
              "name": "minOutlierScore",
              "value": 3,
              "type": "number"
            },
            {
              "id": "limit-assignment",
              "name": "videosPerRun",
              "value": 5,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000012",
      "name": "Niche Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [-420, 540]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://outlierkit.com/api/v1/outliers/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"query\": \"{{ $json.niche }}\",\n  \"limit\": {{ $json.videosPerRun }},\n  \"minOutlierScore\": {{ $json.minOutlierScore }}\n}",
        "options": {}
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000013",
      "name": "Find Outlier Videos (OutlierKit)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [-140, 540]
    },
    {
      "parameters": {
        "jsCode": "// The API responds with { data: { results: [...] } }.\n// Handle a bare { results: [...] } payload too, so the workflow\n// keeps working if the envelope changes.\nconst payload = $input.first().json;\nconst data = payload.data ?? payload;\nconst results = data.results ?? [];\n\nif (!Array.isArray(results) || results.length === 0) {\n  return [];\n}\n\nreturn results.map((video) => ({ json: video }));"
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000014",
      "name": "Extract Outlier Videos",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [100, 540]
    },
    {
      "parameters": {
        "url": "=https://outlierkit.com/api/v1/videos/{{ $json.id || $json.videoId }}/transcript",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000015",
      "name": "Fetch Transcript (OutlierKit)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [340, 540]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge each outlier video's metadata with its transcript\n// into one clean item for the AI prompt and the Sheet row.\nconst video = $('Extract Outlier Videos').item.json;\nconst payload = $json.data ?? $json;\nconst segments = payload.segments ?? payload.transcript ?? [];\nconst transcriptText = Array.isArray(segments)\n  ? segments.map((s) => s.text ?? '').join(' ')\n  : String(segments);\n\nconst videoId = video.id ?? video.videoId ?? '';\nconst channel =\n  typeof video.channel === 'object' && video.channel !== null\n    ? video.channel.title\n    : video.channel;\n\nreturn {\n  json: {\n    videoId,\n    title: video.title ?? '',\n    channel: channel ?? '',\n    views: video.views ?? 0,\n    outlierScore: video.highestValue ?? video.outlierScore ?? 0,\n    videoUrl: videoId ? `https://www.youtube.com/watch?v=${videoId}` : '',\n    // Keep the prompt within model context limits.\n    transcriptText: transcriptText.slice(0, 24000),\n  },\n};"
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000016",
      "name": "Prepare Script Brief",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [580, 540]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "You are an expert scriptwriter for faceless YouTube channels. You study videos that massively over-performed to understand proven audience demand, then write fully original scripts that target the same demand. You never copy or closely paraphrase the source material — plagiarized or lightly-rewritten scripts get channels demonetized.",
              "role": "system"
            },
            {
              "content": "=You write scripts for a faceless YouTube channel in the \"{{ $('Niche Settings').first().json.niche }}\" niche.\n\nHere is a video that massively over-performed its channel's average ({{ $json.outlierScore }}x outlier score, {{ $json.views }} views):\n\nTITLE: {{ $json.title }}\nCHANNEL: {{ $json.channel }}\n\nTRANSCRIPT:\n{{ $json.transcriptText }}\n\nWrite a COMPLETELY ORIGINAL script that targets the same proven demand from a fresh angle.\n\nDeliver, in this order:\n1. Three title options (max 60 characters each)\n2. A one-line thumbnail concept\n3. A full voiceover script (~1,200 words for an 8-10 minute video) with:\n   - HOOK (first 20 seconds, open a curiosity loop)\n   - 3-5 clearly labelled sections\n   - A retention re-hook roughly every 90 seconds\n   - A soft subscribe CTA near the middle, not the end",
              "role": "user"
            }
          ]
        },
        "options": {}
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000017",
      "name": "Draft Script with AI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [820, 540]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $now.format('yyyy-LL-dd') }}",
            "Niche": "={{ $('Niche Settings').first().json.niche }}",
            "Video Title": "={{ $('Prepare Script Brief').item.json.title }}",
            "Channel": "={{ $('Prepare Script Brief').item.json.channel }}",
            "Views": "={{ $('Prepare Script Brief').item.json.views }}",
            "Outlier Score": "={{ $('Prepare Script Brief').item.json.outlierScore }}",
            "Video URL": "={{ $('Prepare Script Brief').item.json.videoUrl }}",
            "Script Draft": "={{ $json.message.content }}"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "f1a7c2e0-1111-4a01-9c01-000000000018",
      "name": "Save to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1080, 540]
    }
  ],
  "connections": {
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Niche Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Monday 7 AM": {
      "main": [
        [
          {
            "node": "Niche Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Niche Settings": {
      "main": [
        [
          {
            "node": "Find Outlier Videos (OutlierKit)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Outlier Videos (OutlierKit)": {
      "main": [
        [
          {
            "node": "Extract Outlier Videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Outlier Videos": {
      "main": [
        [
          {
            "node": "Fetch Transcript (OutlierKit)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Transcript (OutlierKit)": {
      "main": [
        [
          {
            "node": "Prepare Script Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Script Brief": {
      "main": [
        [
          {
            "node": "Draft Script with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Script with AI": {
      "main": [
        [
          {
            "node": "Save to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
