{
  "id": "ayEitoNXtdYvLPfW",
  "name": "Automated Retweet Cleanup for X Accounts with Scheduling",
  "tags": [],
  "nodes": [
    {
      "id": "69913088-8501-421a-b049-fa8d13f36485",
      "name": "TRIGGER | Schedule (09:00 server time)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Runs daily at 09:00 (server timezone). Adjust cadence as needed.",
      "position": [
        -192,
        560
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "439b67e8-ba4a-4711-b3ab-e5b90d64cfc8",
      "name": "CONFIG | User Variables (Set Fields)",
      "type": "n8n-nodes-base.set",
      "notes": "Centralizes user-editable variables for easy setup.",
      "position": [
        112,
        560
      ],
      "parameters": {
        "values": {
          "number": [
            {
              "name": "max_results",
              "value": 50
            },
            {
              "name": "batch_delay_minutes",
              "value": 1
            }
          ],
          "string": [
            {
              "name": "target_username",
              "value": "yourhandle"
            }
          ]
        },
        "options": {},
        "keepOnlySet": true
      },
      "typeVersion": 2
    },
    {
      "id": "90daaa08-e061-40a8-b127-352f41d3ccf6",
      "name": "FETCH | Get user ID by username (X API)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Resolves @handle \u2192 user.id via X API. Uses OAuth2 Credentials (no tokens in node).",
      "position": [
        336,
        560
      ],
      "parameters": {
        "url": "={{ `https://api.twitter.com/2/users/by/username/${$node[\"CONFIG | User Variables (Set Fields)\"].json.target_username}` }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "oAuth2",
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "n8n-workflow"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e7f8d1a4-4554-498c-bd08-608601de57dd",
      "name": "FETCH | Latest tweets (X API)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches recent tweets for the user. Includes fields to detect retweets.",
      "position": [
        592,
        560
      ],
      "parameters": {
        "url": "={{ `https://api.twitter.com/2/users/${$json.data.id}/tweets` }}",
        "options": {},
        "sendQuery": true,
        "authentication": "oAuth2",
        "queryParameters": {
          "parameters": [
            {
              "name": "max_results",
              "value": "={{ $node[\"CONFIG | User Variables (Set Fields)\"].json.max_results }}"
            },
            {
              "name": "tweet.fields",
              "value": "created_at,referenced_tweets"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "237c5e8a-afde-4a31-a1f3-dadabfe4b57c",
      "name": "SPLIT | One item per tweet",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Converts the tweets array into one item per tweet.",
      "position": [
        832,
        560
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "2f57969a-11e0-47ca-aee5-b137c57a5e62",
      "name": "IF | Is retweet?",
      "type": "n8n-nodes-base.if",
      "notes": "Continue only when the first referenced_tweet is of type 'retweeted'.",
      "position": [
        1072,
        560
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-rt",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.referenced_tweets ? $json.referenced_tweets[0].type : '' }}",
              "rightValue": "retweeted"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e2727f92-2da8-4eda-8013-2aa9110651c8",
      "name": "LOOP | Split in batches (rate-limit friendly)",
      "type": "n8n-nodes-base.splitInBatches",
      "notes": "Processes items in small batches to respect API limits.",
      "onError": "continueRegularOutput",
      "position": [
        1312,
        560
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "06dd0960-e777-4dd2-9a3a-0044208d5e46",
      "name": "SEND | Unretweet (delete retweet)",
      "type": "n8n-nodes-base.twitter",
      "notes": "Deletes YOUR retweet; the original tweet remains.",
      "position": [
        1536,
        480
      ],
      "parameters": {
        "operation": "delete",
        "tweetDeleteId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "e8b4b141-caee-4df9-a132-614e82b287ea",
      "name": "WAIT | Delay between API calls",
      "type": "n8n-nodes-base.wait",
      "notes": "Spaces API calls using CONFIG.batch_delay_minutes.",
      "position": [
        1856,
        528
      ],
      "parameters": {
        "unit": "minutes",
        "amount": "={{ $node[\"CONFIG | User Variables (Set Fields)\"].json.batch_delay_minutes }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "b0712253-079a-4f14-b633-64510b75ac2f",
      "name": "\ud83d\udfe8 Sticky: Description (copy of page)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        176
      ],
      "parameters": {
        "color": "yellow",
        "width": 540,
        "height": 992,
        "content": "## Who\u2019s it for\nSocial media managers, creators, and brand accounts that rely on retweets for reach but want an automated, hands-off cleanup after campaigns to keep profiles tidy and on-brand.\n\n## What it does / How it works\nThe workflow runs on a schedule, resolves your handle to a user ID, fetches recent tweets, filters retweets only, and unretweets them safely with batching and delays to respect rate limits. A dedicated CONFIG (Set Fields) node centralizes variables like target_username, max_results, and batch_delay_minutes, so you can adjust behavior without touching logic.\n\n## Use cases\n- Brand hygiene: Auto-unretweet promos after 48\u201372h.\n- Campaign cadence: Remove event retweets once the event ends.\n- Feed freshness: Clear low-priority retweets on a rolling basis.\n\n## How to set up\n1) Open CONFIG (Set Fields) and replace placeholders: { \"target_username\": \"yourhandle\", \"max_results\": 50, \"batch_delay_minutes\": 1 }\n2) In HTTP/Twitter nodes, assign your X (Twitter) OAuth2 credentials (never paste tokens into nodes).\n3) Adjust the schedule and run a test, then enable.\n\n## Requirements\n- n8n (cloud/self-hosted)\n- X developer app with OAuth2 + permissions to read tweets and delete your retweets\n\n## How to customize\n- Raise max_results (up to 100) or change the schedule (cron/interval).\n- Modify the IF condition to target replies/quotes.\n- Add logging/notifications (Slack/Email) after each unretweet.\n\n**Security:** No hardcoded API keys. Use Credentials. Keep delay \u2265 1 minute.\n\n## API endpoints used\n- **GET /2/users/by/username/{username}**\n- **GET /2/users/{id}/tweets** with `tweet.fields=created_at,referenced_tweets`\n\n### Example: GET /2/users/by/username/{username}\n```json\n{\n  \"data\": { \"id\": \"2244994945\", \"name\": \"Twitter Dev\", \"username\": \"TwitterDev\" }\n}\n```\n\n### Example: GET /2/users/{id}/tweets (truncated)\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"1760000000000000000\",\n      \"text\": \"RT @someone: \u2026\",\n      \"referenced_tweets\": [{ \"type\": \"retweeted\", \"id\": \"1759999999999999999\" }],\n      \"created_at\": \"2025-01-15T09:10:11.000Z\"\n    }\n  ],\n  \"meta\": { \"result_count\": 20 }\n}\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "8070f2cd-ded2-47d6-a7a1-261dda506d5f",
      "name": "Sticky: Quick setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        1280
      ],
      "parameters": {
        "color": "white",
        "height": 280,
        "content": "## Quick setup\n- Open **CONFIG (Set Fields)** and fill: `target_username`, `max_results`, `batch_delay_minutes`.\n- Assign **X OAuth2** credentials in HTTP/Twitter nodes.\n- Run once with a sample; then enable."
      },
      "typeVersion": 1
    },
    {
      "id": "49537208-5044-4ebb-9e50-f79ca9287acc",
      "name": "Sticky: Rules & customization",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        1184
      ],
      "parameters": {
        "color": "white",
        "width": 532,
        "height": 280,
        "content": "## Rules & customization\n- Change schedule cadence in **TRIGGER**.\n- Adjust `max_results` up to 100.\n- Edit IF for replies/quotes.\n- Add **ON FAIL** routing (dead letter) + Slack/Email."
      },
      "typeVersion": 1
    },
    {
      "id": "1f5027fc-0ff1-4a0c-a555-225c29314d60",
      "name": "Sticky: Troubleshooting",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        1280
      ],
      "parameters": {
        "color": "white",
        "width": 260,
        "height": 456,
        "content": "## Troubleshooting\n- 401/403 \u2192 credential scopes or expired token.\n- 404 \u2192 invalid tweet_id or missing permission.\n- 429 \u2192 add **Wait** / increase delay; consider smaller batches."
      },
      "typeVersion": 1
    },
    {
      "id": "0b1a83f8-3bb0-4eae-a0bf-2be3bde1ef62",
      "name": "Sticky: TRIGGER details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        160
      ],
      "parameters": {
        "color": "white",
        "height": 516,
        "content": "## TRIGGER | Schedule\n- Runs daily at 09:00 server time.\n- Change cadence: cron or interval.\n- Testing tips: run once manually to push a sample through the whole pipeline.\n\n**Output:** emits a single empty item to start the flow.\n**Failure modes:** none (but cron misconfig = no runs)."
      },
      "typeVersion": 1
    },
    {
      "id": "521b8703-ff91-49ba-9ae8-8da8e481827a",
      "name": "Sticky: CONFIG details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": "white",
        "width": 224,
        "height": 676,
        "content": "## CONFIG | User Variables (Set Fields)\nCentralizes user-editable fields.\n\n**Fields**\n- `target_username` (string) \u2014 your X handle without `@`.\n- `max_results` (number) \u2014 tweets to fetch (\u2264100).\n- `batch_delay_minutes` (number) \u2014 delay between API calls.\n\n**Output JSON**\n```json\n{\n  \"target_username\":\"yourhandle\",\n  \"max_results\":50,\n  \"batch_delay_minutes\":1\n}\n```\n**Tip:** keep all user inputs here to avoid touching logic nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "9f368eb6-d8a8-4f2f-8c8d-b19f94a3d662",
      "name": "Sticky: GET USER details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        48
      ],
      "parameters": {
        "color": "white",
        "width": 256,
        "height": 628,
        "content": "## FETCH | Get user ID by username (HTTP)\n**Purpose:** Resolve `@handle` \u2192 `user.id` via X API.\n\n**Auth:** OAuth2 (Credentials). No tokens in node.\n**Request:** `GET /2/users/by/username/{handle}`\n**Output shape:**\n```json\n{ \"data\": { \"id\": \"123\", \"username\": \"yourhandle\" } }\n```\n**Common errors:**\n- 401/403 \u2192 invalid/expired OAuth2.\n- 404 \u2192 handle not found or suspended.\n**Next:** feeds `data.id` to Fetch Tweets."
      },
      "typeVersion": 1
    },
    {
      "id": "e3927387-c8f4-4a4f-a1a4-d95ee3e7efd9",
      "name": "Sticky: FETCH TWEETS details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        48
      ],
      "parameters": {
        "color": "white",
        "width": 208,
        "height": 628,
        "content": "## FETCH | Latest tweets (HTTP)\n**Purpose:** Pull recent tweets for the resolved user.\n\n**Query params:**\n- `max_results` \u2190 from CONFIG\n- `tweet.fields=created_at,referenced_tweets` (retweet detection)\n\n**Output shape (truncated):**\n```json\n{ \"data\": [ {\"id\":\"...\",\"referenced_tweets\":[{\"type\":\"retweeted\"}]} ] }\n```\n**Edge cases:** empty `data` array; pagination not enabled in this template.\n**Next:** passes array to SPLIT."
      },
      "typeVersion": 1
    },
    {
      "id": "9db3075f-3341-4177-b4c0-42a7f97c7cd6",
      "name": "Sticky: SPLIT details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        192
      ],
      "parameters": {
        "color": "white",
        "height": 484,
        "content": "## SPLIT | One item per tweet\n**Purpose:** Convert list response to single items per tweet.\n\n**Input field:** `data`\n**Output:** each item = one tweet (enables per-item filtering & actions).\n**Tip:** If API changes payload keys, update `fieldToSplitOut`."
      },
      "typeVersion": 1
    },
    {
      "id": "536b8ab0-3da5-4ffd-9029-93367c37a8ad",
      "name": "Sticky: IF RT details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        192
      ],
      "parameters": {
        "color": "white",
        "height": 484,
        "content": "## IF | Is retweet?\n**Condition:**\n`$json.referenced_tweets ? $json.referenced_tweets[0].type : ''` equals `retweeted`.\n\n**True path:** retweets only \u2192 proceed to LOOP.\n**False path:** non-retweets are dropped.\n**Edge cases:** missing `referenced_tweets` \u2192 false branch.\n**Customize:** change to replies/quotes by checking `type`."
      },
      "typeVersion": 1
    },
    {
      "id": "e7fe7eae-8b07-47d9-8003-eb02ee1960b6",
      "name": "Sticky: LOOP details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        192
      ],
      "parameters": {
        "color": "white",
        "width": 224,
        "height": 484,
        "content": "## LOOP | Split in batches\n**Purpose:** Respect rate limits by chunking.\n\n**Behavior:** iterates through items in small groups.\n**Tip:** combine with WAIT for pacing; add error routing if needed.\n**Failure:** on API failure, downstream node can push to dead-letter (not included by default)."
      },
      "typeVersion": 1
    },
    {
      "id": "aab64d61-9f7a-410a-b4c8-f654d9159848",
      "name": "Sticky: UNRETWEET details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        192
      ],
      "parameters": {
        "color": "white",
        "width": 320,
        "height": 484,
        "content": "## SEND | Unretweet (delete retweet)\n**Purpose:** Delete YOUR retweet only.\n\n**Node:** Twitter \u2192 operation: `delete`\n**Input:** `tweetDeleteId = $json.id`\n**Note:** original author\u2019s tweet remains intact.\n**Auth:** Twitter Credential (OAuth2) selected in node.\n**Errors:** 404 when `id` is not a retweet; 403 without write scope."
      },
      "typeVersion": 1
    },
    {
      "id": "70400462-8a65-4d7a-8c3a-c9a826087575",
      "name": "Sticky: WAIT details",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1824,
        192
      ],
      "parameters": {
        "color": "white",
        "width": 320,
        "height": 484,
        "content": "## WAIT | Delay between API calls\n**Purpose:** Space out requests to avoid 429 responses.\n\n**Delay:** uses `batch_delay_minutes` from CONFIG.\n**Recommendation:** keep \u2265 1 minute. Increase during heavy cleanup.\n**Alternative:** Use Wait \u2192 `seconds` for finer control if needed."
      },
      "typeVersion": 1
    },
    {
      "id": "d26b1bc9-6169-48c1-bb74-a6670bc3028d",
      "name": "Sticky: Data flow map",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        1280
      ],
      "parameters": {
        "color": "white",
        "width": 320,
        "height": 484,
        "content": "## Data flow\nTRIGGER \u2192 CONFIG \u2192 GET USER \u2192 FETCH TWEETS \u2192 SPLIT \u2192 IF RT \u2192 LOOP \u2192 UNRETWEET \u2192 WAIT \u2192 LOOP\n\n**Customize hooks:**\n- Add LOG node after UNRETWEET\n- Add ON FAIL branch from LOOP/UNRETWEET to Sheet/Slack\n- Extend IF for keyword/author filters in a prior ENRICH node\n\nNotify path: UNRETWEET \u2192 LOG \u2192 Slack (optional)\nError path: Error Trigger \u2192 DLQ Set \u2192 (Sheets/Email/Slack)"
      },
      "typeVersion": 1
    },
    {
      "id": "f13bd3c3-5739-47bc-9b75-faf07f0c4ea9",
      "name": "NOTIFY | Slack (optional)",
      "type": "n8n-nodes-base.slack",
      "notes": "Optional: Send a Slack message after each unretweet. Set credentials and channel.",
      "position": [
        2016,
        528
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "channel": "={{ '#your-channel' }}",
        "attachments": [],
        "otherOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "54dac7b0-8123-4580-a9a7-899a53b3ca4a",
      "name": "ON ERROR | Capture",
      "type": "n8n-nodes-base.errorTrigger",
      "notes": "Catches workflow errors at runtime.",
      "position": [
        16,
        1056
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "832a1fed-ce14-4dc7-bad2-9260fe1d9833",
      "name": "DLQ | Format error (Set)",
      "type": "n8n-nodes-base.set",
      "notes": "Formats an error record for a dead-letter destination (e.g., Google Sheets, Email, or Slack).",
      "position": [
        288,
        1056
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "failed_node",
              "value": "={{ $json.execution.error.node.name }}"
            },
            {
              "name": "error_message",
              "value": "={{ $json.execution.error.message }}"
            },
            {
              "name": "timestamp",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        },
        "options": {},
        "keepOnlySet": true
      },
      "typeVersion": 2
    },
    {
      "id": "1ce4bb84-0bf9-4dcb-b1bc-6e4f81836d14",
      "name": "Sticky: DLQ & Notify",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        1280
      ],
      "parameters": {
        "color": "white",
        "width": 420,
        "height": 368,
        "content": "## Dead-letter & notifications (optional)\n- **Error Trigger** captures runtime errors and forwards to `DLQ | Format error (Set)`.\n- From there, connect to Google Sheets/Email/Slack to persist and alert.\n- **Notify path**: after `SEND | Unretweet`, a log message is built and can be sent to Slack.\n\n**Tip:** Use this to audit API failures (403/404/429) and track actions."
      },
      "typeVersion": 1
    },
    {
      "id": "f86fbe64-7c65-439f-91cc-a78c57727adc",
      "name": "Sticky: API endpoints",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1280
      ],
      "parameters": {
        "color": "white",
        "width": 420,
        "height": 300,
        "content": "## API endpoints (reference)\n- `GET https://api.twitter.com/2/users/by/username/{username}` \u2192 resolve user id\n- `GET https://api.twitter.com/2/users/{id}/tweets?tweet.fields=created_at,referenced_tweets` \u2192 list tweets\n\n**Response payloads** are shown in the yellow master sticky."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4d73778e-74ab-49ab-af4b-ec2aa8149324",
  "connections": {
    "IF | Is retweet?": {
      "main": [
        [
          {
            "node": "LOOP | Split in batches (rate-limit friendly)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ON ERROR | Capture": {
      "main": [
        [
          {
            "node": "DLQ | Format error (Set)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NOTIFY | Slack (optional)": {
      "main": [
        [
          {
            "node": "LOOP | Split in batches (rate-limit friendly)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SPLIT | One item per tweet": {
      "main": [
        [
          {
            "node": "IF | Is retweet?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FETCH | Latest tweets (X API)": {
      "main": [
        [
          {
            "node": "SPLIT | One item per tweet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WAIT | Delay between API calls": {
      "main": [
        [
          {
            "node": "NOTIFY | Slack (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SEND | Unretweet (delete retweet)": {
      "main": [
        [
          {
            "node": "WAIT | Delay between API calls",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CONFIG | User Variables (Set Fields)": {
      "main": [
        [
          {
            "node": "FETCH | Get user ID by username (X API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "TRIGGER | Schedule (09:00 server time)": {
      "main": [
        [
          {
            "node": "CONFIG | User Variables (Set Fields)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FETCH | Get user ID by username (X API)": {
      "main": [
        [
          {
            "node": "FETCH | Latest tweets (X API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LOOP | Split in batches (rate-limit friendly)": {
      "main": [
        [
          {
            "node": "SEND | Unretweet (delete retweet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}