This workflow corresponds to n8n.io template #17634 — we link there as the canonical source.
This workflow follows the HTTP Request → RSS Feed Read recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "U6uNSUE71F0lbvTd",
"name": "CREATE_YOUTUBE_SHORTS_FROM_TRENDS_N8N_COMMUNITY_TEMPLATE_V5_OFFICIAL_STICKY_STYLE",
"tags": [],
"nodes": [
{
"id": "e51f68ea-4afd-46ce-ad17-2548aee0960d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": "#F7FDFC",
"width": 520,
"height": 1104,
"content": "## Create YouTube Shorts from trends with OpenAI, Airtable and Creatomate\n\n### How it works\n\n1. Collects popular YouTube videos and current Google Trends topics.\n2. Normalises and ranks factual commentary opportunities with OpenAI.\n3. Creates Airtable review records and waits for human approval of content and usage rights.\n4. Sends approved records to Creatomate and monitors the render.\n5. Updates Airtable with the final video URL or a useful error.\n\n### Setup steps\n\n- [ ] Add the YouTube API key and Airtable IDs in `DISCOVERY \u2014 Runtime Config`.\n- [ ] Select OpenAI and Airtable credentials in the Discovery nodes.\n- [ ] Configure Airtable IDs and the renderer webhook in `APPROVAL \u2014 Community Setup`.\n- [ ] Add the Creatomate template ID and polling settings in `RENDER \u2014 Community Setup`.\n- [ ] Select Creatomate and Airtable Header Auth credentials in the Render nodes.\n- [ ] Use a vertical Creatomate template with dynamic elements named `Hook` and `Supporting_Text`.\n- [ ] Test Render, then Approval, then Discovery before activating schedules.\n\n### Requirements\n\nYouTube Data API, OpenAI, Airtable, Creatomate and an n8n instance with a public production webhook URL.\n\n### Customization\n\nChange the region, schedules, channel theme, ranking prompt, result limits, approved rights values, copy limits, polling interval and Creatomate design.\n\nThis workflow requires human review of usage rights and does not automatically publish to social platforms."
},
"typeVersion": 1
},
{
"id": "2875076a-0ef3-4aff-b64a-eb702f8d4906",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
0
],
"parameters": {
"color": 7,
"width": 480,
"height": 544,
"content": "## Configure discovery\n\nChoose manual or scheduled execution, then set the YouTube API key, Airtable IDs, region, channel theme and result limits in the Runtime Config node."
},
"typeVersion": 1
},
{
"id": "12c6d4a1-4841-4e22-bbf0-e7bdb557658c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
16
],
"parameters": {
"color": 7,
"width": 768,
"height": 544,
"content": "## Collect and normalise trends\n\nFetches YouTube Most Popular videos and Google Trends RSS in parallel, converts both sources to one schema and merges the resulting signals."
},
"typeVersion": 1
},
{
"id": "06298cd2-5575-46b8-abe1-67f0835dfdb7",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2176,
112
],
"parameters": {
"color": 7,
"width": 1328,
"height": 352,
"content": "## Rank ideas and create reviews\n\nBuilds a candidate pack, asks OpenAI to rank factual commentary opportunities and creates Airtable records with Status set to Review."
},
"typeVersion": 1
},
{
"id": "c12ae379-b62d-44f3-ac14-3a781a2d9bb1",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
960
],
"parameters": {
"color": 7,
"width": 1600,
"height": 512,
"content": "## Find and validate approvals\n\nFinds Airtable records approved by a person and checks required content and rights fields before rendering."
},
"typeVersion": 1
},
{
"id": "fc00f176-ac71-4587-a94c-9be6edd8cb1f",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2384,
944
],
"parameters": {
"color": 7,
"width": 1056,
"height": 576,
"content": "## Queue and send approved records\n\nMarks valid records as Queued, rebuilds the approved payload and sends it to the renderer. Invalid records are marked Failed."
},
"typeVersion": 1
},
{
"id": "018f170f-9e18-4913-aad2-feac0517e39a",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
1840
],
"parameters": {
"color": 7,
"width": 2720,
"height": 608,
"content": "## Submit and monitor the render\n\nValidates the webhook payload, submits the Creatomate job and polls its status. Invalid requests receive an immediate response."
},
"typeVersion": 1
},
{
"id": "ea6e15d7-f537-45ff-9933-7307de3cd4b0",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3504,
1776
],
"parameters": {
"color": 7,
"width": 1056,
"height": 768,
"content": "## Record the final outcome\n\nSuccessful jobs update Airtable with the render ID and URL. Processing jobs retry, while terminal errors update Error Log."
},
"typeVersion": 1
},
{
"id": "80b79a14-4bca-491e-9f54-b41d7e134a52",
"name": "DISCOVERY \u2014 Manual Test",
"type": "n8n-nodes-base.manualTrigger",
"position": [
672,
224
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b5714778-97ca-4cba-856b-656ed4a12746",
"name": "DISCOVERY \u2014 Run Three Times Daily",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
672,
384
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7,13,19 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "740e5f3a-3034-44d1-b48e-5bdd2e7b8492",
"name": "DISCOVERY \u2014 Runtime Config",
"type": "n8n-nodes-base.set",
"position": [
960,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e797fad2-2feb-4a2f-8d92-d6775212df31",
"name": "airtable_table_id",
"type": "string",
"value": "YOUR_AIRTABLE_TABLE_ID"
},
{
"id": "560fb178-77f8-49f8-bcf2-4cfafc55f6d8",
"name": "airtable_base_id",
"type": "string",
"value": "YOUR_AIRTABLE_BASE_ID"
},
{
"id": "5d4df1f4-78a5-4795-a5cc-981881078027",
"name": "youtube_api_key",
"type": "string",
"value": "YOUR_YOUTUBE_API_KEY"
},
{
"id": "ac20166d-4da7-46e4-9677-43466075aa0c",
"name": "region_code",
"type": "string",
"value": "GB"
},
{
"id": "8db0bf85-1f2f-4da7-9c2d-084cdaff5fff",
"name": "max_youtube_results",
"type": "number",
"value": 25
},
{
"id": "1a7b8f07-3a2d-4434-9e47-0e3c6c494871",
"name": "max_candidates",
"type": "number",
"value": 5
},
{
"id": "26c11ea2-6597-44d7-a45d-47e23bc082db",
"name": "channel_theme",
"type": "string",
"value": "Fast, factual short-form explainers using original commentary"
},
{
"id": "ab91af10-a7ac-45bc-829f-1fab7f30defa",
"name": "run_id",
"type": "string",
"value": "={{ 'shorts-' + $now.toISO() }}"
},
{
"id": "ff70fc69-5b98-4ab6-b74b-1d403033d681",
"name": "created_at",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6439bdc3-35ea-4815-b9ae-947762a1cf4e",
"name": "DISCOVERY \u2014 Fetch YouTube Most Popular",
"type": "n8n-nodes-base.httpRequest",
"position": [
1312,
192
],
"parameters": {
"url": "https://www.googleapis.com/youtube/v3/videos",
"options": {
"timeout": 30000
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "part",
"value": "snippet,statistics,contentDetails"
},
{
"name": "chart",
"value": "mostPopular"
},
{
"name": "regionCode",
"value": "={{ $json.region_code }}"
},
{
"name": "maxResults",
"value": "={{ $json.max_youtube_results }}"
},
{
"name": "key",
"value": "={{ $('DISCOVERY \u2014 Runtime Config').first().json.youtube_api_key }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "5716e623-488e-43f4-917b-d7302c8f2b1a",
"name": "DISCOVERY \u2014 Fetch Google Trends RSS",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
1312,
400
],
"parameters": {
"url": "=https://trends.google.com/trending/rss?geo={{ $('DISCOVERY \u2014 Runtime Config').first().json.region_code }}",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "98febd10-1e85-4e55-b7eb-f28a375631fb",
"name": "DISCOVERY \u2014 Normalise YouTube Signals",
"type": "n8n-nodes-base.code",
"position": [
1600,
192
],
"parameters": {
"jsCode": "const cfg = $('DISCOVERY \u2014 Runtime Config').first().json;\nconst items = Array.isArray($json.items) ? $json.items : [];\n\nreturn items.map(v => ({\n json: {\n run_id: cfg.run_id,\n created_at: cfg.created_at,\n source_type: 'youtube',\n source_id: v.id,\n topic: v.snippet?.title || '',\n source_url: `https://www.youtube.com/watch?v=${v.id}`,\n channel: v.snippet?.channelTitle || '',\n published_at: v.snippet?.publishedAt || '',\n description: String(v.snippet?.description || '').slice(0, 1000),\n views: Number(v.statistics?.viewCount || 0),\n likes: Number(v.statistics?.likeCount || 0),\n comments: Number(v.statistics?.commentCount || 0),\n duration: v.contentDetails?.duration || '',\n rights_status: 'REVIEW_REQUIRED'\n }\n}));"
},
"typeVersion": 2
},
{
"id": "acd2873b-1b83-4df5-a145-4371939b8894",
"name": "DISCOVERY \u2014 Normalise Google Trends Signals",
"type": "n8n-nodes-base.code",
"position": [
1600,
400
],
"parameters": {
"jsCode": "const cfg = $('DISCOVERY \u2014 Runtime Config').first().json;\nreturn $input.all().map((item, index) => {\n const j = item.json || {};\n return {\n json: {\n run_id: cfg.run_id,\n created_at: cfg.created_at,\n source_type: 'google_trends',\n source_id: `trend-${index + 1}`,\n topic: String(j.title || '').trim(),\n source_url: String(j.link || j.guid || '').trim(),\n channel: 'Google Trends UK',\n published_at: j.isoDate || j.pubDate || '',\n description: String(j.contentSnippet || j.content || j.description || '').replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim().slice(0, 1000),\n views: 0,\n likes: 0,\n comments: 0,\n duration: '',\n rights_status: 'RESEARCH_ONLY'\n }\n };\n}).filter(i => i.json.topic);"
},
"typeVersion": 2
},
{
"id": "1cc87db4-6b87-4a2b-9a30-d363a9f40366",
"name": "DISCOVERY \u2014 Merge Trend Signals",
"type": "n8n-nodes-base.merge",
"position": [
1872,
304
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "bd934e66-fa4b-4ec8-bab8-fcd0534cf916",
"name": "DISCOVERY \u2014 Prepare Candidate Pack",
"type": "n8n-nodes-base.code",
"position": [
2240,
304
],
"parameters": {
"jsCode": "const rows = $input.all().map(i => i.json);\nconst seen = new Set();\nconst clean = [];\n\nfor (const row of rows) {\n const key = `${row.source_type}|${String(row.topic || '').toLowerCase().trim()}`;\n if (!row.topic || seen.has(key)) continue;\n seen.add(key);\n\n const engagement = row.views > 0\n ? ((row.likes + row.comments) / row.views) * 100\n : 0;\n\n clean.push({\n ...row,\n engagement_rate: Number(engagement.toFixed(3))\n });\n}\n\nclean.sort((a, b) => {\n if (a.source_type !== b.source_type) return a.source_type === 'youtube' ? -1 : 1;\n return (b.views || 0) - (a.views || 0);\n});\n\nconst youtube = clean.filter(r => r.source_type === 'youtube').slice(0, 30);\nconst trends = clean.filter(r => r.source_type === 'google_trends').slice(0, 20);\nconst candidates = [...youtube, ...trends];\n\nreturn [{\n json: {\n run_id: $('DISCOVERY \u2014 Runtime Config').first().json.run_id,\n created_at: $('DISCOVERY \u2014 Runtime Config').first().json.created_at,\n channel_theme: $('DISCOVERY \u2014 Runtime Config').first().json.channel_theme,\n candidate_count: candidates.length,\n candidates,\n candidates_text: candidates.map((r, i) =>\n `${i + 1}. ${r.topic}\\nType: ${r.source_type}\\nURL: ${r.source_url}\\nChannel: ${r.channel}\\nPublished: ${r.published_at}\\nViews: ${r.views}\\nLikes: ${r.likes}\\nComments: ${r.comments}\\nEngagement: ${r.engagement_rate}%\\nDescription: ${r.description}`\n ).join('\\n\\n')\n }\n}];"
},
"typeVersion": 2
},
{
"id": "123effc1-947c-443e-800e-875a030e88b8",
"name": "DISCOVERY \u2014 Rank Short Opportunities with OpenAI",
"type": "n8n-nodes-base.httpRequest",
"position": [
2512,
304
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {
"timeout": 180000
},
"jsonBody": "={{\n{\n \"model\": \"gpt-5-mini\",\n \"input\": \"Return valid JSON only.\\n\\nSelect the best five trend opportunities for an original commentary-led YouTube Shorts channel. Do not recommend reposting. Reject unsafe, unverifiable or unsuitable topics.\\n\\nReturn exactly: {\\\"items\\\":[{\\\"topic\\\":\\\"\\\",\\\"source_url\\\":\\\"\\\",\\\"source_type\\\":\\\"\\\",\\\"trend_score\\\":0,\\\"controversy_score\\\":0,\\\"risk_level\\\":\\\"low|medium|high\\\",\\\"rights_status\\\":\\\"REVIEW_REQUIRED|RESEARCH_ONLY|DO_NOT_USE\\\",\\\"recommended_angle\\\":\\\"\\\",\\\"hook\\\":\\\"\\\",\\\"script\\\":\\\"\\\",\\\"decision\\\":\\\"review|reject\\\",\\\"reason\\\":\\\"\\\"}]}\\n\\nRules: select no more than five items; script 55-80 words; hook no more than 8 words; lead with one concrete fact; use only candidate facts; YouTube footage remains REVIEW_REQUIRED.\\n\\nCANDIDATES:\\n\" + $json.candidates_text\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"typeVersion": 4.2
},
{
"id": "10cd109c-60e0-471e-b90c-086291e13de6",
"name": "DISCOVERY \u2014 Parse Ranked Candidates",
"type": "n8n-nodes-base.code",
"position": [
2800,
304
],
"parameters": {
"jsCode": "const response = $json || {};\nlet text = response.output_text || '';\n\nif (!text && Array.isArray(response.output)) {\n for (const out of response.output) {\n for (const c of (out.content || [])) {\n if (typeof c.text === 'string') {\n text = c.text;\n break;\n }\n }\n if (text) break;\n }\n}\n\nif (!text) throw new Error('OpenAI ranking response did not contain output text.');\n\nconst parsed = JSON.parse(text.replace(/```json|```/g, '').trim());\nconst items = Array.isArray(parsed.items) ? parsed.items : [];\nconst cfg = $('DISCOVERY \u2014 Runtime Config').first().json;\n\nreturn items\n .filter(x => x && x.decision === 'review' && Number(x.trend_score || 0) >= 60)\n .slice(0, Number(cfg.max_candidates || 10))\n .map((x, index) => ({\n json: {\n run_id: cfg.run_id,\n created_at: cfg.created_at,\n candidate_rank: index + 1,\n topic: String(x.topic || ''),\n source_url: String(x.source_url || ''),\n source_type: String(x.source_type || ''),\n trend_score: Number(x.trend_score || 0),\n controversy_score: Number(x.controversy_score || 0),\n risk_level: String(x.risk_level || 'medium'),\n rights_status: String(x.rights_status || 'REVIEW_REQUIRED'),\n angle: String(x.recommended_angle || ''),\n hook: String(x.hook || ''),\n script: String(x.script || ''),\n clip_start: String(x.clip_start || ''),\n clip_end: String(x.clip_end || ''),\n status: 'Review',\n reason: String(x.reason || '')\n }\n }));"
},
"typeVersion": 2
},
{
"id": "89022b17-e5ea-442e-8ea0-f04158f9dea1",
"name": "DISCOVERY \u2014 Create Airtable Review Record",
"type": "n8n-nodes-base.httpRequest",
"position": [
3072,
304
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('DISCOVERY \u2014 Runtime Config').first().json.airtable_base_id }}/{{ $('DISCOVERY \u2014 Runtime Config').first().json.airtable_table_id }}",
"method": "POST",
"options": {
"timeout": 30000
},
"jsonBody": "={{\n{\n \"fields\": {\n \"Run_ID\": $json.run_id,\n \"Topic\": $json.topic,\n \"Source_URL\": $json.source_url,\n \"Source_Type\": $json.source_type,\n \"Trend_Score\": $json.trend_score,\n \"Controversy_Score\": $json.controversy_score,\n \"Rights_Status\": $json.rights_status,\n \"Risk_Level\": $json.risk_level,\n \"Angle\": $json.angle,\n \"Hook\": $json.hook,\n \"Script\": $json.script,\n \"Clip_Start\": $json.clip_start,\n \"Clip_End\": $json.clip_end,\n \"Status\": $json.status,\n \"Error_Log\": $json.reason,\n \"Created_At\": $json.created_at\n },\n \"typecast\": true\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "54fffb13-1260-49f2-84c5-d6482917eaf3",
"name": "DISCOVERY \u2014 Trend Branch Complete",
"type": "n8n-nodes-base.noOp",
"position": [
3360,
304
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8c289328-93c9-4bd8-965b-b1c4c1b26718",
"name": "APPROVAL \u2014 Every Minute",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
672,
1296
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 1
}
]
}
},
"typeVersion": 1.2
},
{
"id": "7ed51356-ff4b-43be-ac09-df5cf79a83be",
"name": "APPROVAL \u2014 Find Approved Airtable Records",
"type": "n8n-nodes-base.httpRequest",
"position": [
1232,
1216
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_base_id }}/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_table_id }}",
"options": {
"timeout": 30000
},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "filterByFormula",
"value": "AND({Status}='Approved',OR({Render_Job_ID}='',{Render_Job_ID}=BLANK()))"
},
{
"name": "maxRecords",
"value": "={{ $('APPROVAL \u2014 Community Setup').first().json.max_records_per_run }}"
},
{
"name": "sort[0][field]",
"value": "Created_At"
},
{
"name": "sort[0][direction]",
"value": "asc"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "867a5265-17c2-468c-adac-fb1aec501fbf",
"name": "APPROVAL \u2014 Expand Airtable Records",
"type": "n8n-nodes-base.code",
"position": [
1520,
1216
],
"parameters": {
"jsCode": "const records = Array.isArray($json.records) ? $json.records : [];\n\nreturn records.map(record => {\n const f = record.fields || {};\n\n return {\n json: {\n airtable_record_id: record.id,\n topic: String(f.Topic || '').trim(),\n source_url: String(f.Source_URL || '').trim(),\n rights_status: String(f.Rights_Status || '').trim(),\n hook: String(f.Hook || '').trim(),\n script: String(f.Script || '').trim(),\n current_status: String(f.Status || '').trim()\n }\n };\n});"
},
"typeVersion": 2
},
{
"id": "3d04522a-76d5-460f-b8a5-1d8f302b9b86",
"name": "APPROVAL \u2014 Validate Approved Record",
"type": "n8n-nodes-base.code",
"position": [
1792,
1216
],
"parameters": {
"jsCode": "const allowed = [\n 'OWNED',\n 'LICENSED',\n 'CREATIVE_COMMONS_REVIEWED',\n 'PLATFORM_REMIX_ALLOWED',\n 'COMMENTARY_USE_APPROVED'\n];\n\nconst item = {\n ...$json,\n rights_status: String($json.rights_status || '').trim().toUpperCase()\n};\n\nconst required = [\n 'airtable_record_id',\n 'topic',\n 'source_url',\n 'rights_status',\n 'hook',\n 'script'\n];\n\nconst missing = required.filter(key => !String(item[key] || '').trim());\n\nreturn [{\n json: {\n ...item,\n valid: missing.length === 0 && allowed.includes(item.rights_status),\n validation_error: missing.length\n ? `Missing fields: ${missing.join(', ')}`\n : allowed.includes(item.rights_status)\n ? ''\n : `Rights status not approved: ${item.rights_status}`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "8284ac60-594d-4ef8-bb90-5151f935020d",
"name": "APPROVAL \u2014 IF Record Valid",
"type": "n8n-nodes-base.if",
"position": [
2080,
1216
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5c4ead04-1d06-4560-b1d7-ca1cee538fe1",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.valid }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "5d49da14-10e5-4e73-8450-8a861da56a77",
"name": "APPROVAL \u2014 Mark Record Queued",
"type": "n8n-nodes-base.httpRequest",
"position": [
2432,
1120
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_base_id }}/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_table_id }}/{{ $json.airtable_record_id }}",
"method": "PATCH",
"options": {
"timeout": 30000
},
"jsonBody": "={{\n{\n \"fields\": {\n \"Status\": \"Queued\",\n \"Error_Log\": \"\"\n },\n \"typecast\": true\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "e40142ea-d9a8-4f8f-955b-7918e2d8d38c",
"name": "APPROVAL \u2014 Reattach Approval Payload",
"type": "n8n-nodes-base.code",
"position": [
2720,
1120
],
"parameters": {
"jsCode": "const record = $input.first().json;\nconst fields = record.fields || {};\n\nreturn [{\n json: {\n reference_id: String(record.id || ''),\n topic: String(fields.Topic || ''),\n source: {\n url: String(fields.Source_URL || ''),\n rights_status: String(fields.Rights_Status || '').trim().toUpperCase()\n },\n editorial: {\n hook: String(fields.Hook || ''),\n script: String(fields.Script || '')\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f1a0dce0-7835-4a61-a532-5eeb22cd2dce",
"name": "APPROVAL \u2014 Send to Creatomate Renderer",
"type": "n8n-nodes-base.httpRequest",
"position": [
2992,
1120
],
"parameters": {
"url": "={{ $('APPROVAL \u2014 Community Setup').first().json.renderer_webhook_url }}",
"method": "POST",
"options": {
"timeout": 600000
},
"jsonBody": "={{ {\n reference_id: $json.reference_id,\n topic: $json.topic,\n source: {\n url: $json.source.url,\n rights_status: $json.source.rights_status\n },\n editorial: {\n hook: $json.editorial.hook,\n script: $json.editorial.script\n }\n} }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "07801087-a062-4a59-b52e-a47cc7851993",
"name": "APPROVAL \u2014 Trigger Complete",
"type": "n8n-nodes-base.noOp",
"position": [
3280,
1120
],
"parameters": {},
"typeVersion": 1
},
{
"id": "460ad079-edf6-4d0d-a14e-c1e80cfa26ce",
"name": "APPROVAL \u2014 Mark Trigger Failed",
"type": "n8n-nodes-base.httpRequest",
"position": [
2432,
1344
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_base_id }}/{{ $('APPROVAL \u2014 Community Setup').first().json.airtable_table_id }}/{{ $json.airtable_record_id }}",
"method": "PATCH",
"options": {
"timeout": 30000
},
"jsonBody": "={{\n{\n \"fields\": {\n \"Status\": \"Failed\",\n \"Error_Log\": $json.validation_error\n },\n \"typecast\": true\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "ed93ab95-65f0-4da7-a280-14e5ac868dfb",
"name": "APPROVAL \u2014 Community Setup",
"type": "n8n-nodes-base.set",
"position": [
960,
1216
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3078c075-d69d-491b-9494-9818fab86248",
"name": "airtable_base_id",
"type": "string",
"value": "YOUR_AIRTABLE_BASE_ID"
},
{
"id": "80165bb7-c58a-402e-ada2-6bab57b4f77c",
"name": "airtable_table_id",
"type": "string",
"value": "YOUR_AIRTABLE_TABLE_ID"
},
{
"id": "15ce5202-0bf3-4504-a328-cec1e3dad68b",
"name": "renderer_webhook_url",
"type": "string",
"value": "https://YOUR_N8N_HOST/webhook/creatomate-approved-short-render"
},
{
"id": "c307b990-6363-4d8e-a21b-666933386d8f",
"name": "max_records_per_run",
"type": "number",
"value": 3
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e540602a-23cd-4788-af5a-0ed2f863b1c5",
"name": "RENDER \u2014 Creatomate Render Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
672,
2112
],
"parameters": {
"path": "creatomate-approved-short-render",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "f19c9c29-ee5d-4edb-aa82-27d13295df56",
"name": "RENDER \u2014 Validate Approved Payload",
"type": "n8n-nodes-base.code",
"position": [
1232,
2112
],
"parameters": {
"jsCode": "const body = $json.body || $json;\n\nconst allowed = [\n 'OWNED',\n 'LICENSED',\n 'CREATIVE_COMMONS_REVIEWED',\n 'PLATFORM_REMIX_ALLOWED',\n 'COMMENTARY_USE_APPROVED'\n];\n\nconst required = ['reference_id', 'topic', 'source', 'editorial'];\n\nconst missing = required.filter(\n key => body[key] === undefined || body[key] === null\n);\n\nif (missing.length) {\n return [{\n json: {\n valid: false,\n statusCode: 400,\n error: `Missing: ${missing.join(', ')}`\n }\n }];\n}\n\nconst sourceUrl = String(body.source?.url || '').trim();\n\nconst rightsStatus = String(\n body.source?.rights_status || ''\n)\n .trim()\n .toUpperCase();\n\nconst topic = String(body.topic || '').trim();\nconst hook = String(body.editorial?.hook || '').trim();\nconst script = String(body.editorial?.script || '').trim();\n\nif (!sourceUrl) {\n return [{\n json: {\n valid: false,\n statusCode: 400,\n error: 'Missing source.url'\n }\n }];\n}\n\nif (!allowed.includes(rightsStatus)) {\n return [{\n json: {\n valid: false,\n statusCode: 403,\n error: `Rights status not approved: ${rightsStatus}`\n }\n }];\n}\n\nconst words = script\n .split(/\\s+/)\n .filter(Boolean);\n\nif (words.length < 25) {\n return [{\n json: {\n valid: false,\n statusCode: 400,\n error: 'Editorial script is too short.'\n }\n }];\n}\n\n/**\n * Prevent decimal values such as:\n * 3.58 million\n * 2.11%\n * 9.45%\n *\n * from being split as sentence endings.\n */\nconst protectedScript = script.replace(\n /(\\d)\\.(\\d)/g,\n '$1<DECIMAL>$2'\n);\n\n/**\n * Split the script into sentences.\n */\nconst sentences =\n protectedScript\n .replace(/\\s+/g, ' ')\n .match(/[^.!?]+[.!?]+|[^.!?]+$/g)\n ?.map(sentence =>\n sentence\n .replace(/<DECIMAL>/g, '.')\n .trim()\n )\n .filter(Boolean) || [script];\n\n/**\n * Limit text by word count rather than character count.\n * This avoids long paragraphs and cuts at natural word boundaries.\n */\nconst limitWords = (value, maxWords) => {\n const textWords = String(value || '')\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (textWords.length <= maxWords) {\n return textWords.join(' ');\n }\n\n return `${textWords\n .slice(0, maxWords)\n .join(' ')}\u2026`;\n};\n\n/**\n * Scene 1 remains the hook.\n *\n * Scene 2:\n * Opening context and initial evidence.\n *\n * Scene 3:\n * Supporting detail or explanation.\n *\n * Scene 4:\n * Final point, implication or CTA.\n */\nconst scene2Source = sentences\n .slice(0, 2)\n .join(' ');\n\nconst scene3Source = sentences\n .slice(2, 4)\n .join(' ');\n\nconst scene4Source = sentences\n .slice(4)\n .join(' ');\n\n/**\n * Fallbacks ensure no scene is left blank when the script\n * contains fewer sentences than expected.\n */\nconst scene2Text = limitWords(\n scene2Source || script,\n 12\n);\n\nconst scene3Text = limitWords(\n scene3Source ||\n sentences.slice(1).join(' ') ||\n script,\n 12\n);\n\nconst scene4Text = limitWords(\n scene4Source ||\n 'Follow for more.',\n 10\n);\n\nreturn [{\n json: {\n valid: true,\n\n reference_id: String(body.reference_id),\n\n topic,\n\n source_url: sourceUrl,\n\n rights_status: rightsStatus,\n\n hook: limitWords(\n hook || topic,\n 8\n ),\n\n script,\n\n scene_2_text: scene2Text,\n\n scene_3_text: scene3Text,\n\n scene_4_text: scene4Text,\n\n poll_attempt: 0\n }\n}];"
},
"typeVersion": 2
},
{
"id": "8ff42842-5da4-4085-a230-d275fcb5b5ca",
"name": "RENDER \u2014 IF Payload Valid",
"type": "n8n-nodes-base.if",
"position": [
1520,
2112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9c819d0f-ff20-4212-baa0-08633d5e205a",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.valid }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "7defea78-381c-44d3-b6b2-49f03ac81599",
"name": "RENDER \u2014 Store Render Context",
"type": "n8n-nodes-base.code",
"position": [
2080,
2016
],
"parameters": {
"jsCode": "const approval = $('RENDER \u2014 Validate Approved Payload').first().json;\n\n// Creatomate can be returned as a single render object by n8n,\n// or as the first item when the API response is an array.\nconst renderId = $json?.id || $json?.data?.id || '';\n\nif (!renderId) {\n throw new Error(`Creatomate did not return a render id. Response: ${JSON.stringify($json)}`);\n}\n\nreturn [{\n json: {\n ...approval,\n render_id: String(renderId),\n task_id: String(renderId),\n render_status: String($json?.status || 'planned'),\n poll_attempt: 0\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0d8e4fd4-db43-4fdd-921d-ab153f42a460",
"name": "RENDER \u2014 Wait Before Status Check",
"type": "n8n-nodes-base.wait",
"position": [
2352,
2016
],
"parameters": {
"amount": "={{ $('RENDER \u2014 Community Setup').first().json.poll_seconds }}"
},
"typeVersion": 1.1
},
{
"id": "616706ab-f827-4873-a679-50c13f9561b5",
"name": "RENDER \u2014 Evaluate Render Status",
"type": "n8n-nodes-base.code",
"position": [
2912,
2016
],
"parameters": {
"jsCode": "const ctx = $('RENDER \u2014 Store Render Context').first().json;\n\nlet priorAttempt = Number(ctx.poll_attempt || 0);\ntry {\n if ($('RENDER \u2014 Increment Render Poll Attempt').isExecuted) {\n priorAttempt = Number($('RENDER \u2014 Increment Render Poll Attempt').last().json.poll_attempt || priorAttempt);\n }\n} catch (_) {}\n\nconst status = String($json?.status || '').toLowerCase();\nconst processingStatuses = ['planned', 'waiting', 'transcribing', 'rendering'];\n\nreturn [{\n json: {\n ...ctx,\n poll_attempt: priorAttempt,\n render_status: status || 'unknown',\n render_succeeded: status === 'succeeded',\n render_failed: status === 'failed',\n still_processing: processingStatuses.includes(status),\n render_url: String($json?.url || ''),\n render_error: String(\n $json?.error_message ||\n $json?.error ||\n (status === 'failed' ? 'Creatomate render failed.' : '')\n ),\n raw_render: $json\n }\n}];"
},
"typeVersion": 2
},
{
"id": "29d69911-7b45-4b9b-a762-53fd1cae324f",
"name": "RENDER \u2014 IF Render Succeeded",
"type": "n8n-nodes-base.if",
"position": [
3200,
2016
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2e22c7a7-aca6-4c72-9d1c-69812ffb02f3",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.render_succeeded }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "ab6013e6-4fbf-41b7-b7a2-e360fbaaa8d0",
"name": "RENDER \u2014 Prepare Render Success",
"type": "n8n-nodes-base.code",
"position": [
3552,
1952
],
"parameters": {
"jsCode": "if (!$json.render_url) {\n throw new Error('Creatomate returned succeeded without a render URL.');\n}\n\nreturn [{\n json: {\n reference_id: $json.reference_id,\n task_id: $json.render_id,\n render_id: $json.render_id,\n status: 'Rendered',\n render_url: $json.render_url,\n topic: $json.topic,\n poll_attempt: $json.poll_attempt\n }\n}];"
},
"typeVersion": 2
},
{
"id": "5d63b4c0-04b2-4c80-9fee-535e6d4ed472",
"name": "RENDER \u2014 Update Airtable Rendered",
"type": "n8n-nodes-base.httpRequest",
"position": [
3840,
1952
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('RENDER \u2014 Community Setup').first().json.airtable_base_id }}/{{ $('RENDER \u2014 Community Setup').first().json.airtable_table_id }}/{{ $json.reference_id }}",
"method": "PATCH",
"options": {
"timeout": 30000
},
"jsonBody": "={{\n{\n \"fields\": {\n \"Status\": \"Rendered\",\n \"Render_Job_ID\": $json.render_id,\n \"Render_URL\": $json.render_url,\n \"Error_Log\": \"\"\n },\n \"typecast\": true\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "34d9739d-33f9-4749-a50e-99b29c20d38c",
"name": "RENDER \u2014 Respond Rendered",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4112,
1952
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "={{ { accepted: true, status: 'Rendered', job_id: $('RENDER \u2014 Prepare Render Success').first().json.render_id, render_url: $('RENDER \u2014 Prepare Render Success').first().json.render_url } }}"
},
"typeVersion": 1.4
},
{
"id": "ca88c12e-a8cc-417c-b86c-f8eaab49189f",
"name": "RENDER \u2014 Increment Render Poll Attempt",
"type": "n8n-nodes-base.code",
"position": [
3552,
2224
],
"parameters": {
"jsCode": "const attempt = Number($json.poll_attempt || 0) + 1;\nconst retryAllowed = Boolean($json.still_processing) && attempt < Number($('RENDER \u2014 Community Setup').first().json.max_poll_attempts || 10);\n\nreturn [{\n json: {\n ...$json,\n poll_attempt: attempt,\n retry_allowed: retryAllowed,\n error_log: retryAllowed\n ? ''\n : ($json.render_error || `Creatomate did not complete after ${attempt} polling attempts. Last status: ${$json.render_status || 'unknown'}`)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c634d115-1636-40f7-9d9a-428852aafba8",
"name": "RENDER \u2014 IF Retry Allowed",
"type": "n8n-nodes-base.if",
"position": [
3840,
2224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9d5b5b74-7f9a-4bca-92d2-2e71bd8fc4f4",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.retry_allowed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "9c012fa6-30ea-41e2-ac95-9e4706f12329",
"name": "RENDER \u2014 Update Airtable Failed",
"type": "n8n-nodes-base.httpRequest",
"position": [
4112,
2384
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $('RENDER \u2014 Community Setup').first().json.airtable_base_id }}/{{ $('RENDER \u2014 Community Setup').first().json.airtable_table_id }}/{{ $json.reference_id }}",
"method": "PATCH",
"options": {
"timeout": 30000
},
"jsonBody": "={{\n{\n \"fields\": {\n \"Status\": \"Failed\",\n \"Render_Job_ID\": $json.render_id || $json.task_id || \"\",\n \"Error_Log\": $json.error_log || $json.render_error || \"Creatomate render failed.\"\n },\n \"typecast\": true\n}\n}}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "124c4a84-2add-4a21-955f-a7a0e8e7cddd",
"name": "RENDER \u2014 Respond Render Failed",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4400,
2384
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "={{ { accepted: false, status: 'Failed', job_id: $('RENDER \u2014 Increment Render Poll Attempt').first().json.render_id || '', error: $('RENDER \u2014 Increment Render Poll Attempt').first().json.error_log || 'Creatomate render failed.' } }}"
},
"typeVersion": 1.4
},
{
"id": "023ed633-cd72-45cc-87db-e5bbf1cd75bc",
"name": "RENDER \u2014 Respond Invalid",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1792,
2272
],
"parameters": {
"options": {
"responseCode": "={{ $json.statusCode || 400 }}"
},
"respondWith": "json",
"responseBody": "={{ { accepted: false, error: $json.error } }}"
},
"typeVersion": 1.4
},
{
"id": "723f4c2e-9396-4b16-877f-f9607bfd7af3",
"name": "RENDER \u2014 Submit Creatomate Render",
"type": "n8n-nodes-base.httpRequest",
"position": [
1792,
2016
],
"parameters": {
"url": "https://api.creatomate.com/v2/renders",
"method": "POST",
"options": {
"timeout": 120000
},
"jsonBody": "={{ {\n template_id: $('RENDER \u2014 Community Setup').first().json.creatomate_template_id,\n modifications: {\n \"Hook.text\": ($json.hook || $json.topic || \"\")\n .split(/\\s+/)\n .slice(0, 8)\n .join(\" \"),\n \"Supporting_Text.text\": [\n $json.scene_2_text,\n $json.scene_3_text\n ]\n .filter(Boolean)\n .join(\" \")\n .split(/\\s+/)\n .slice(0, 12)\n .join(\" \")\n },\n metadata: JSON.stringify({\n airtable_record_id: $json.reference_id || \"\",\n source: \"trend-to-shorts\",\n template: \"quick-promo\"\n })\n} }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "132cf5a5-a1c3-4043-b244-306b5cd8be8b",
"name": "RENDER \u2014 Get Creatomate Render",
"type": "n8n-nodes-base.httpRequest",
"position": [
2640,
2016
],
"parameters": {
"url": "=https://api.creatomate.com/v2/renders/{{ $('RENDER \u2014 Store Render Context').first().json.render_id }}",
"options": {
"timeout": 120000
},
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "dab97c2e-826f-41b8-bc85-b4a3312eae8b",
"name": "RENDER \u2014 Community Setup",
"type": "n8n-nodes-base.set",
"position": [
960,
2016
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "937fcb40-5705-4fde-afed-0c3a70481e30",
"name": "airtable_base_id",
"type": "string",
"value": "YOUR_AIRTABLE_BASE_ID"
},
{
"id": "edb40c30-b6c2-46d3-87b5-0f7694e82124",
"name": "airtable_table_id",
"type": "string",
"value": "YOUR_AIRTABLE_TABLE_ID"
},
{
"id": "5cf73f95-eb58-4e2b-a6d4-87becb58b5cb",
"name": "creatomate_template_id",
"type": "string",
"value": "YOUR_CREATOMATE_TEMPLATE_ID"
},
{
"id": "64796886-3655-4112-b0b3-2cfa44d8d5f5",
"name": "poll_seconds",
"type": "number",
"value": 45
},
{
"id": "3c0f2207-7b13-4eed-a4f4-c46abf2e94e5",
"name": "max_poll_attempts",
"type": "number",
"value": 10
},
{
"id": "0075fff5-d994-4476-b640-2ccf37da55e7",
"name": "render_url_field",
"type": "string",
"value": "Render_URL"
}
]
}
},
"typeVersion": 3.4
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "e8967533-fe8a-4a95-9760-e52993340941",
"nodeGroups": [],
"connections": {
"APPROVAL \u2014 Every Minute": {
"main": [
[
{
"node": "APPROVAL \u2014 Community Setup",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Manual Test": {
"main": [
[
{
"node": "DISCOVERY \u2014 Runtime Config",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Community Setup": {
"main": [
[
{
"node": "RENDER \u2014 Validate Approved Payload",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 IF Payload Valid": {
"main": [
[
{
"node": "RENDER \u2014 Submit Creatomate Render",
"type": "main",
"index": 0
}
],
[
{
"node": "RENDER \u2014 Respond Invalid",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 IF Retry Allowed": {
"main": [
[
{
"node": "RENDER \u2014 Wait Before Status Check",
"type": "main",
"index": 0
}
],
[
{
"node": "RENDER \u2014 Update Airtable Failed",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Community Setup": {
"main": [
[
{
"node": "APPROVAL \u2014 Find Approved Airtable Records",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 IF Record Valid": {
"main": [
[
{
"node": "APPROVAL \u2014 Mark Record Queued",
"type": "main",
"index": 0
}
],
[
{
"node": "APPROVAL \u2014 Mark Trigger Failed",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Runtime Config": {
"main": [
[
{
"node": "DISCOVERY \u2014 Fetch YouTube Most Popular",
"type": "main",
"index": 0
},
{
"node": "DISCOVERY \u2014 Fetch Google Trends RSS",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 IF Render Succeeded": {
"main": [
[
{
"node": "RENDER \u2014 Prepare Render Success",
"type": "main",
"index": 0
}
],
[
{
"node": "RENDER \u2014 Increment Render Poll Attempt",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Mark Record Queued": {
"main": [
[
{
"node": "APPROVAL \u2014 Reattach Approval Payload",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Store Render Context": {
"main": [
[
{
"node": "RENDER \u2014 Wait Before Status Check",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Get Creatomate Render": {
"main": [
[
{
"node": "RENDER \u2014 Evaluate Render Status",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Merge Trend Signals": {
"main": [
[
{
"node": "DISCOVERY \u2014 Prepare Candidate Pack",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Evaluate Render Status": {
"main": [
[
{
"node": "RENDER \u2014 IF Render Succeeded",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Prepare Render Success": {
"main": [
[
{
"node": "RENDER \u2014 Update Airtable Rendered",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Update Airtable Failed": {
"main": [
[
{
"node": "RENDER \u2014 Respond Render Failed",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Run Three Times Daily": {
"main": [
[
{
"node": "DISCOVERY \u2014 Runtime Config",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Submit Creatomate Render": {
"main": [
[
{
"node": "RENDER \u2014 Store Render Context",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Update Airtable Rendered": {
"main": [
[
{
"node": "RENDER \u2014 Respond Rendered",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Wait Before Status Check": {
"main": [
[
{
"node": "RENDER \u2014 Get Creatomate Render",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Expand Airtable Records": {
"main": [
[
{
"node": "APPROVAL \u2014 Validate Approved Record",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Prepare Candidate Pack": {
"main": [
[
{
"node": "DISCOVERY \u2014 Rank Short Opportunities with OpenAI",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Creatomate Render Webhook": {
"main": [
[
{
"node": "RENDER \u2014 Community Setup",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Validate Approved Payload": {
"main": [
[
{
"node": "RENDER \u2014 IF Payload Valid",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Validate Approved Record": {
"main": [
[
{
"node": "APPROVAL \u2014 IF Record Valid",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Fetch Google Trends RSS": {
"main": [
[
{
"node": "DISCOVERY \u2014 Normalise Google Trends Signals",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Parse Ranked Candidates": {
"main": [
[
{
"node": "DISCOVERY \u2014 Create Airtable Review Record",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Reattach Approval Payload": {
"main": [
[
{
"node": "APPROVAL \u2014 Send to Creatomate Renderer",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Normalise YouTube Signals": {
"main": [
[
{
"node": "DISCOVERY \u2014 Merge Trend Signals",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Send to Creatomate Renderer": {
"main": [
[
{
"node": "APPROVAL \u2014 Trigger Complete",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Fetch YouTube Most Popular": {
"main": [
[
{
"node": "DISCOVERY \u2014 Normalise YouTube Signals",
"type": "main",
"index": 0
}
]
]
},
"RENDER \u2014 Increment Render Poll Attempt": {
"main": [
[
{
"node": "RENDER \u2014 IF Retry Allowed",
"type": "main",
"index": 0
}
]
]
},
"APPROVAL \u2014 Find Approved Airtable Records": {
"main": [
[
{
"node": "APPROVAL \u2014 Expand Airtable Records",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Create Airtable Review Record": {
"main": [
[
{
"node": "DISCOVERY \u2014 Trend Branch Complete",
"type": "main",
"index": 0
}
]
]
},
"DISCOVERY \u2014 Normalise Google Trends Signals": {
"main": [
[
{
"node": "DISCOVERY \u2014 Merge Trend Signals",
"type": "main",
"index": 1
}
]
]
},
"DISCOVERY \u2014 Rank Short Opportunities with OpenAI": {
"main": [
[
{
"node": "DISCOVERY \u2014 Parse Ranked Candidates",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow discovers trending topics from YouTube Most Popular and Google Trends, uses OpenAI to rank and draft Shorts-ready angles, stores items for review in Airtable, then sends approved records to Creatomate to render a video and writes the render status and URL back to…
Source: https://n8n.io/workflows/17634/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Monitors multiple technology websites 24/7 Uses AI to read and understand each article Filters out low-quality content automatically. Saves the best articles to Notion with summaries. Sends you Telegr
Turn your latest blog posts into Pinterest-ready pin titles, descriptions and keywords, exported as a CSV formatted for Pinterest's native bulk upload. An AI writes one conversion-focused pin per post
This workflow helps you repurpose your YouTube videos across multiple social media platforms with zero manual effort. It’s designed for creators, businesses, and marketers who want to maximize reach w
Triggered by a GitLab MR webhook, this workflow automatically assists your team in writing customer-facing release notes by combining Linear issue data with Claude AI.
Transform YouTube Videos to Social Media Content with Vizard AI and GPT‑4.1