This workflow corresponds to n8n.io template #9748 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets 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": "6SOSDrfhrqAX7DBS",
"name": "Deduplicate and auto-post X videos with AI captions from a user",
"tags": [],
"nodes": [
{
"id": "b4721eab-d2f9-4687-be1b-15ef709f4eb9",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"notes": "\u8a2d\u5b9a\u3057\u305f\u6642\u9593\u9593\u9694\u3067\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u81ea\u52d5\u7684\u306b\u958b\u59cb\u3059\u308b\u30c8\u30ea\u30ac\u30fc\u3067\u3059\u3002",
"position": [
-160,
256
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "ced6b0eb-8a23-4a07-aba2-140f9b1c0480",
"name": "Get User ID",
"type": "n8n-nodes-base.httpRequest",
"notes": "\u6307\u5b9a\u30e6\u30fc\u30b6\u30fc\u540d\u304b\u3089 API \u3067\u5229\u7528\u3059\u308b user.id \u3092\u53d6\u5f97\u3057\u307e\u3059\u3002",
"position": [
64,
256
],
"parameters": {
"url": "https://api.twitter.com/2/users/by/username/XXXXXXXXX",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "n8n-workflow"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "fd64a087-90ed-43c9-8305-358dd1b58b40",
"name": "Get Tweets with Videos",
"type": "n8n-nodes-base.httpRequest",
"notes": "\u30e6\u30fc\u30b6\u30fc\u306e\u6700\u65b0\u30c4\u30a4\u30fc\u30c8\uff08\u30e1\u30c7\u30a3\u30a2\u542b\u3080\uff09\u3092\u53d6\u5f97\u3057\u307e\u3059\u3002",
"position": [
288,
256
],
"parameters": {
"url": "={{ 'https://api.twitter.com/2/users/' + $json.data.id + '/tweets?max_results=10&tweet.fields=attachments&expansions=attachments.media_keys&media.fields=type,url,variants' }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "69b14da0-3280-4c55-bc50-c4b7f355af23",
"name": "Filter Video Tweets",
"type": "n8n-nodes-base.code",
"notes": "\u52d5\u753b\u30e1\u30c7\u30a3\u30a2\u306e\u307f\u62bd\u51fa\u3057\u307e\u3059\u3002",
"position": [
512,
256
],
"parameters": {
"jsCode": "const tweets = $input.first().json.data || [];\nconst includes = $input.first().json.includes || {};\nconst media = includes.media || [];\nconst out = [];\nfor (const t of tweets) {\n const keys = (t.attachments && t.attachments.media_keys) || [];\n const hasVideo = keys.some(k => (media.find(m => m.media_key===k) || {}).type === 'video');\n if (hasVideo) {\n out.push({ tweet_id: t.id, text: t.text, url: `https://twitter.com/i/status/${t.id}` });\n }\n}\nreturn out;"
},
"typeVersion": 2
},
{
"id": "5ae1e3f9-4821-41ea-b88e-c9041d7a933a",
"name": "Check Existing URLs",
"type": "n8n-nodes-base.googleSheets",
"notes": "\u5c65\u6b74\u7ba1\u7406\u306e\u305f\u3081\u30b7\u30fc\u30c8\u306b\u8ffd\u8a18\u3057\u307e\u3059\u3002",
"position": [
736,
256
],
"parameters": {
"columns": {
"value": {
"URL": "={{ $json.url }}",
"\u6587\u7ae0": "={{ $json.text }}",
"\u30c4\u30a4\u30fc\u30c8ID": "={{ $json.tweet_id }}"
},
"schema": [
{
"id": "\u30c4\u30a4\u30fc\u30c8ID",
"type": "string",
"displayName": "\u30c4\u30a4\u30fc\u30c8ID",
"canBeUsedToMatch": true
},
{
"id": "\u6587\u7ae0",
"type": "string",
"displayName": "\u6587\u7ae0",
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"displayName": "URL",
"canBeUsedToMatch": true
},
{
"id": "setURL",
"type": "string",
"displayName": "setURL",
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"row_number"
]
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 89155752,
"cachedResultName": "\u66f4\u65b0\u60c5\u5831"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "19pJoBuba1o2PaJlx0inaGnGi45PpxbfB79MMhwLFH4s",
"cachedResultName": "1.\u6295\u7a3f\u81ea\u52d5\u53ce\u96c6"
}
},
"typeVersion": 4.5
},
{
"id": "d974879e-f343-4375-b521-4ff6f5780a9e",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"notes": "URL \u3092\u5f15\u7528\u52d5\u753b\u5f62\u5f0f\u3078\u5909\u63db\u3057\u307e\u3059\u3002",
"position": [
960,
256
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1",
"name": "=setURL",
"type": "string",
"value": "={{ $json.URL }}/video/1"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "325148aa-46c7-447c-badf-5193c567e622",
"name": "Append or update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"notes": "setURL \u3092 upsert \u3057\u307e\u3059\u3002",
"position": [
1184,
256
],
"parameters": {
"columns": {
"value": {
"setURL": "={{ $json.setURL }}",
"\u30c4\u30a4\u30fc\u30c8ID": "={{ $('Check Existing URLs').item.json['\u30c4\u30a4\u30fc\u30c8ID'] }}"
},
"schema": [
{
"id": "\u30c4\u30a4\u30fc\u30c8ID",
"type": "string",
"displayName": "\u30c4\u30a4\u30fc\u30c8ID",
"canBeUsedToMatch": true
},
{
"id": "setURL",
"type": "string",
"displayName": "setURL",
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"\u30c4\u30a4\u30fc\u30c8ID"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 89155752,
"cachedResultName": "\u66f4\u65b0\u60c5\u5831"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=19pJoBuba1o2PaJlx0inaGnGi45PpxbfB79MMhwLFH4s"
}
},
"typeVersion": 4.7
},
{
"id": "380ba318-3a78-488e-9b63-4f820a56d568",
"name": "Check New Videos",
"type": "n8n-nodes-base.if",
"notes": "\u672a\u5b8c\u4e86\u306e\u307f\u6b21\u3078\u6d41\u3057\u307e\u3059\u3002",
"position": [
1408,
256
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "notEqual"
},
"leftValue": "={{ $('Append or update row in sheet').item.json['\u9054\u6210\u72b6\u6cc1'] || '' }}",
"rightValue": "\u5b8c\u4e86"
}
]
}
},
"typeVersion": 2
},
{
"id": "5c7f361a-2e63-4eca-9107-a8d7135194f3",
"name": "Generate Tweet Text",
"type": "@n8n/n8n-nodes-langchain.agent",
"notes": "AI \u304c\u6295\u7a3f\u6587\u3092\u751f\u6210\u3057\u307e\u3059\u3002",
"position": [
1632,
256
],
"parameters": {
"text": "={{ $json.text }}\n\n\u3053\u306e\u5185\u5bb9\u306b\u57fa\u3065\u3044\u3066\u3001\u9b45\u529b\u7684\u306a\u6295\u7a3f\u6587\u3092\u65e5\u672c\u8a9e\u3067\u751f\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u4ee5\u4e0b\u306e\u8981\u7d20\u3092\u542b\u3081\u3066\u304f\u3060\u3055\u3044\uff1a\n- \u7c21\u6f54\u3067\u8208\u5473\u3092\u5f15\u304f\u8aac\u660e\n- \u9069\u5207\u306a\u30cf\u30c3\u30b7\u30e5\u30bf\u30b0\uff083-5\u500b\uff09\n- \u30a8\u30f3\u30b2\u30fc\u30b8\u30e1\u30f3\u30c8\u3092\u4fc3\u3059\u8981\u7d20\n\n\u6587\u5b57\u6570\u306f100-150\u6587\u5b57\u7a0b\u5ea6\u3067\u3001\u6700\u5f8c\u306b\u52d5\u753bURL\u3092\u914d\u7f6e\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
"options": {
"systemMessage": "\u3042\u306a\u305f\u306f\u30bd\u30fc\u30b7\u30e3\u30eb\u30e1\u30c7\u30a3\u30a2\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u4f5c\u6210\u306e\u5c02\u9580\u5bb6\u3067\u3059\u3002\u63d0\u4f9b\u3055\u308c\u305f\u5185\u5bb9\u304b\u3089\u9b45\u529b\u7684\u3067\u5171\u6709\u3055\u308c\u3084\u3059\u3044\u6295\u7a3f\u6587\u3092\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002"
},
"promptType": "define"
},
"typeVersion": 2
},
{
"id": "44c907e2-890c-4d6e-beb4-34c0bfc22e8d",
"name": "Post to X",
"type": "n8n-nodes-base.twitter",
"notes": "\u81ea\u52d5\u3067 X \u306b\u6295\u7a3f\u3057\u307e\u3059\u3002",
"position": [
1984,
256
],
"parameters": {
"text": "={{ $json.output }}\n\n{{ $('Append or update row in sheet').item.json.setURL }}",
"additionalFields": {}
},
"typeVersion": 2
},
{
"id": "16e954d5-8e49-43f8-94a1-90b867531bdc",
"name": "Update Spreadsheet",
"type": "n8n-nodes-base.googleSheets",
"notes": "\u6295\u7a3f\u5b8c\u4e86\u3092\u30de\u30fc\u30af\u3057\u307e\u3059\u3002",
"position": [
2208,
256
],
"parameters": {
"columns": {
"value": {
"\u9054\u6210\u72b6\u6cc1": "\u5b8c\u4e86",
"\u30c4\u30a4\u30fc\u30c8ID": "={{ $('Append or update row in sheet').item.json['\u30c4\u30a4\u30fc\u30c8ID'] }}"
},
"mappingMode": "defineBelow",
"matchingColumns": [
"\u30c4\u30a4\u30fc\u30c8ID"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 89155752,
"cachedResultName": "\u66f4\u65b0\u60c5\u5831"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "19pJoBuba1o2PaJlx0inaGnGi45PpxbfB79MMhwLFH4s"
}
},
"typeVersion": 4.5
},
{
"id": "ea0648dc-eda5-417c-a3e1-d5ca7b925c20",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"notes": "LLM \u306e\u8cc7\u683c\u60c5\u5831\u3092\u53c2\u7167\u3057\u307e\u3059\u3002",
"position": [
1696,
480
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "f850486d-8ec9-4a1a-842e-46ca8a92c1b2",
"name": "\ud83d\udfe8 Sticky: Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
32
],
"parameters": {
"color": "yellow",
"width": 396,
"height": 260,
"content": "## Template Overview (Read Me First)\n**Purpose**: Fetch recent X videos from a target user, generate a fresh caption with AI, and auto-post to your X account. All runs are logged to Google Sheets for deduping & auditing.\n\n### Requirements\n- X (Twitter) OAuth2 credential\n- Google Sheets OAuth2 credential\n- OpenRouter credential (LLM)\n\n### Flow\n1) Schedule \u2192 2) Get user.id \u2192 3) Get tweets (with media) \u2192 4) Filter videos \u2192 5) Append to Sheet \u2192\n6) Build shareable `setURL` \u2192 7) AI caption \u2192 8) Post to X \u2192 9) Mark `\u9054\u6210\u72b6\u6cc1=\u5b8c\u4e86`.\n\n### Security\n- Never hardcode tokens in HTTP nodes\u2014use **Credentials**.\n- Replace demo Sheet with your own; remove personal IDs before publishing.\n"
},
"typeVersion": 1
},
{
"id": "2e27e014-ffbe-4cbb-ba7a-e4ecb7016121",
"name": "Sticky: Schedule Trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
-256,
-16
],
"parameters": {
"color": "white",
"width": 268,
"height": 260,
"content": "## Schedule Trigger \u2014 How to use\n- Polling cadence: e.g., every **1\u20133 hours** (interval) or switch to **Cron** for fine control.\n- Consider API rate limits & off-peak scheduling.\n- For manual runs during tests, click **Execute Workflow**.\n"
},
"typeVersion": 1
},
{
"id": "ff7793b2-6ed8-4dd9-8cf9-bf094084beff",
"name": "Sticky: Get User ID",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
432
],
"parameters": {
"color": "white",
"width": 300,
"height": 308,
"content": "## Get User ID (HTTP Request)\n- Endpoint: `/2/users/by/username/:handle` returns `data.id`.\n- **Action**: Replace the hardcoded username with your target handle (or feed it via a Set node / env var).\n- **Creds**: Use **Twitter OAuth2**. Do not paste bearer tokens directly.\n- Output fields used later: `data.id`.\n"
},
"typeVersion": 1
},
{
"id": "27854651-0068-49d9-87f8-06f4523bf950",
"name": "Sticky: Get Tweets with Videos",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
-48
],
"parameters": {
"color": "white",
"width": 380,
"height": 292,
"content": "## Get Tweets with Videos (HTTP Request)\n- Endpoint: `/2/users/:id/tweets` with expansions `attachments.media_keys` and `media.fields`.\n- Adjust `max_results` (5\u201320). Add `since_id` or `start_time` if you want stricter deduping.\n- Output contains `data[]`, `includes.media[]` for downstream filtering.\n- Tip: Use `tweet.fields=created_at` if you need time-based rules.\n"
},
"typeVersion": 1
},
{
"id": "4f5b8177-7605-40b0-9d7a-b714d21155e0",
"name": "Sticky: Filter Video Tweets",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
-16
],
"parameters": {
"color": "white",
"width": 300,
"height": 260,
"content": "## Filter Video Tweets (Code)\n- Filters items where any attached media has `type === 'video'`.\n- To include GIF videos, also accept `animated_gif`.\n- Emits one item per video tweet with `{ tweet_id, text, url }`.\n- Customize: add language filters, exclude replies/retweets, etc.\n"
},
"typeVersion": 1
},
{
"id": "65afa322-a9e1-464b-8cee-1fcccb4222b6",
"name": "Sticky: Check Existing URLs",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
432
],
"parameters": {
"color": "white",
"width": 396,
"height": 260,
"content": "## Check Existing URLs (Google Sheets \u2014 Append)\n- Appends `{ \u30c4\u30a4\u30fc\u30c8ID, \u6587\u7ae0, URL }` to your Sheet to maintain a registry.\n- Replace `documentId/sheetName` with your own copy.\n- Use this sheet to prevent duplicates and enable audit trails.\n- Tip: Add timestamp & account columns for more context.\n"
},
"typeVersion": 1
},
{
"id": "b3a11fdc-4781-41db-83d7-4b95cc5bd080",
"name": "Sticky: Edit Fields",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
0
],
"parameters": {
"color": "white",
"width": 284,
"height": 260,
"content": "## Edit Fields (Set)\n- Builds `setURL = {{URL}}/video/1` to point to the video playback UI.\n- This is optimized for native X videos; external links may require custom rules.\n- You can add UTM params here for analytics (e.g., `?utm_source=n8n`).\n"
},
"typeVersion": 1
},
{
"id": "a34e1c1d-e631-4de1-baad-818a28dad740",
"name": "Sticky: Append/Update (setURL)",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
480
],
"parameters": {
"color": "white",
"width": 332,
"height": 260,
"content": "## Append or update row in sheet (Google Sheets \u2014 Upsert)\n- Upserts `setURL` by key `\u30c4\u30a4\u30fc\u30c8ID` (**matchingColumns**).\n- Ensures the shareable URL is stored for posting and logging.\n- Remove real Sheet IDs before publishing a public template.\n"
},
"typeVersion": 1
},
{
"id": "064badd6-d0e8-411c-9170-e814952123c0",
"name": "Sticky: Check New Videos",
"type": "n8n-nodes-base.stickyNote",
"position": [
1280,
-32
],
"parameters": {
"color": "white",
"width": 284,
"height": 260,
"content": "## Check New Videos (IF)\n- Condition: proceed only when `\u9054\u6210\u72b6\u6cc1` is not `'\u5b8c\u4e86'` (or row missing).\n- This prevents reposting the same tweet repeatedly.\n- Customize: add checks like age (`created_at`) or language before posting.\n"
},
"typeVersion": 1
},
{
"id": "9d337b5b-8b19-4702-9119-c70f3ab977a6",
"name": "Sticky: OpenRouter Chat Model",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
608
],
"parameters": {
"color": "white",
"width": 348,
"height": 260,
"content": "## OpenRouter Chat Model (Credential)\n- Select model (cost/quality tradeoff). Examples: `gpt-4o-mini`, etc.\n- Configure **OpenRouter** credentials in n8n \u2192 Credentials.\n- Optional: set retry rules in workflow settings for robustness.\n"
},
"typeVersion": 1
},
{
"id": "3204d240-cf45-421b-8f37-43b2aede2bf7",
"name": "Sticky: Generate Tweet Text",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
-32
],
"parameters": {
"color": "white",
"width": 268,
"height": 260,
"content": "## Generate Tweet Text (AI Agent)\n- Prompt: 100\u2013150 chars, 3\u20135 hashtags, and an engagement CTA in Japanese.\n- Provide brand voice via constants (Set node) or via the Sheet.\n- Add compliance rules as needed (e.g., disclaimers, \u7981\u6b62\u8a9e\u8f9e\u66f8).\n"
},
"typeVersion": 1
},
{
"id": "f26c937b-e425-4e59-bcc6-4e3b3d63145e",
"name": "Sticky: Post to X",
"type": "n8n-nodes-base.stickyNote",
"position": [
1920,
-16
],
"parameters": {
"color": "white",
"width": 252,
"height": 260,
"content": "## Post to X (Twitter)\n- Posts `output` from AI plus the `setURL` generated earlier.\n- Test with a sandbox account to validate formatting & line breaks.\n- Consider adding a **dry-run** boolean to skip posting during tests.\n"
},
"typeVersion": 1
},
{
"id": "96c75931-3078-40b8-a730-8ae0f4e005e3",
"name": "Sticky: Update Spreadsheet",
"type": "n8n-nodes-base.stickyNote",
"position": [
2192,
400
],
"parameters": {
"color": "white",
"width": 252,
"height": 276,
"content": "## Update Spreadsheet (Google Sheets)\n- Upserts `\u9054\u6210\u72b6\u6cc1 = \u5b8c\u4e86` for the posted `\u30c4\u30a4\u30fc\u30c8ID`.\n- Add a `posted_at` timestamp and `posted_text` columns for complete logs.\n- This supports idempotency and future analytics.\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "1068413b-38d0-45c7-ac4f-81edd645b61a",
"connections": {
"Post to X": {
"main": [
[
{
"node": "Update Spreadsheet",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Append or update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Get User ID": {
"main": [
[
{
"node": "Get Tweets with Videos",
"type": "main",
"index": 0
}
]
]
},
"Check New Videos": {
"main": [
[
{
"node": "Generate Tweet Text",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get User ID",
"type": "main",
"index": 0
}
]
]
},
"Check Existing URLs": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Filter Video Tweets": {
"main": [
[
{
"node": "Check Existing URLs",
"type": "main",
"index": 0
}
]
]
},
"Generate Tweet Text": {
"main": [
[
{
"node": "Post to X",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Generate Tweet Text",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get Tweets with Videos": {
"main": [
[
{
"node": "Filter Video Tweets",
"type": "main",
"index": 0
}
]
]
},
"Append or update row in sheet": {
"main": [
[
{
"node": "Check New Videos",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Who’s it for
Source: https://n8n.io/workflows/9748/ — 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.
This workflow is for beauty salons who want consistent, high‑quality social media content without writing every post manually. It also suits agencies and automation builders who manage multiple beauty
This n8n workflow turns a script and character/setting description from Google Sheets into a complete stitched UGC-style video ad, fully automated from intake to final delivery.
This workflow automates the creation, rendering, approval, and posting of TikTok-style POV (Point of View) videos to Instagram, with cross-posting to Facebook and YouTube. It eliminates manual video p
This workflow is designed for marketers, content creators, agencies, and solo founders who want to publish long‑form posts with visuals on autopilot using n8n and AI agents.
Who Is This For?