{
  "name": "Send SEC Form 4 insider trading alerts from AlphaAI to Discord",
  "nodes": [
    {
      "parameters": {
        "content": "## Send SEC Form 4 insider trading alerts from AlphaAI to Discord\n\n### How it works\n\n1. A Schedule Trigger runs the workflow every 30 minutes.\n2. An HTTP Request node pulls the insider feed from the AlphaAI news API, covering SEC EDGAR Form 4 insider trades and institutional stake stories.\n3. A Code node keeps material filings scored 6 or higher, skips filings posted in earlier runs and builds color coded embed cards showing Buy or Sell, the holding type and a link to the filing.\n4. An HTTP Request node posts the cards to a Discord channel webhook as one message.\n\n### Setup steps\n\n- [ ] Create a free AlphaAI API key at alphai.io (Account, API keys).\n- [ ] On the \"Fetch Insider News\" node, add a Bearer Auth credential and paste the key without the \"Bearer \" prefix.\n- [ ] In Discord, create a channel webhook (Channel, Edit, Integrations, Webhooks) and paste its URL into the \"Post Alerts to Discord\" node.\n- [ ] Activate the workflow.\n\n### Customization\n\nAdjust the schedule interval or the score threshold (MIN_SCORE) in the \"Build Discord Alert Cards\" node.",
        "width": 480,
        "height": 800
      },
      "id": "23c0646a-4130-48ad-a698-d7bc6e68301f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -448,
        -176
      ]
    },
    {
      "parameters": {
        "content": "## Fetch insider filings\n\nRuns every 30 minutes and pulls the AlphaAI insider feed of SEC Form 4 trades and institutional stake stories.",
        "width": 416,
        "height": 336,
        "color": 7
      },
      "id": "6e99c226-1298-4334-9574-9f246c919980",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        112,
        -176
      ]
    },
    {
      "parameters": {
        "content": "## Build alert cards\n\nKeeps filings scored 6 or higher, dedupes across runs and builds Buy and Sell cards.",
        "width": 240,
        "height": 336,
        "color": 7
      },
      "id": "f4028554-3318-45ed-b6d2-de953f191044",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        576,
        -176
      ]
    },
    {
      "parameters": {
        "content": "## Deliver to Discord\n\nPosts all cards to a channel webhook in one message, with retries.",
        "width": 240,
        "height": 336,
        "color": 7
      },
      "id": "d8761046-d522-4763-b44b-4e6d6dbfd682",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        848,
        -176
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "id": "b0000000-0000-4000-8000-0000000000b3",
      "name": "Every 30 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        160,
        0
      ]
    },
    {
      "parameters": {
        "url": "https://api.alphai.io/api/news/insider/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "options": {}
      },
      "id": "c0000000-0000-4000-8000-0000000000c3",
      "name": "Fetch Insider News",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        380,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// AlphaAI insider (SEC Form 4) -> Discord rich embeds.\n// One message per run: header + up to 10 cards, each linking to the filing's\n// page on alphai.io. Colours by detected Buy/Sell, shows direct/indirect holding\n// form. Dedupes by uid across runs; overflow posts next run. One POST per run.\n\n// \u25bc tune this: minimum relevance score to alert on (endpoint already floors at 4).\n// The insider score scales with transaction value \u2014 6 skips the tiny trades.\nconst MIN_SCORE = 6;\n\nfunction slugify(text) {\n  return (text || '').toLowerCase()\n    .replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/-+/g, '-').trim();\n}\nfunction dateForUrl(iso) {\n  const d = new Date(iso);\n  if (isNaN(d.getTime())) { return ''; }\n  return String(d.getUTCMonth() + 1).padStart(2, '0') + '-' + String(d.getUTCDate()).padStart(2, '0');\n}\nfunction trunc(s, n) { s = (s == null ? '' : String(s)); return s.length > n ? s.slice(0, n - 1) + '\u2026' : s; }\n\nconst SITE = 'https://alphai.io';\nconst GREEN = 3066993, RED = 15158332, GREY = 9807270;\n\nconst incoming = $input.all();\nlet articles = [];\nfor (const it of incoming) {\n  const j = it.json;\n  if (Array.isArray(j)) { articles = articles.concat(j); }\n  else if (j && Array.isArray(j.results)) { articles = articles.concat(j.results); }\n  else if (j) { articles.push(j); }\n}\n\nconst store = $getWorkflowStaticData('global');\nstore.postedUids = store.postedUids || [];\nconst seen = new Set(store.postedUids);\n\nconst embeds = [];\nconst postedUids = [];\nlet budget = 0;\n\nfor (const a of articles) {\n  if (embeds.length >= 10) { break; }\n  const enr = (a && a.enrichment) || {};\n  const org = (a && a.original) || {};\n  const uid = org.uid;\n  const score = enr.relevance_score || 0;\n  const tickers = enr.tickers || [];\n  if (!uid || seen.has(uid)) { continue; }\n  if (score < MIN_SCORE || tickers.length === 0) { continue; }\n\n  // Detect Buy/Sell from the filing's templated text (best-effort).\n  const hay = ((org.title || '') + ' ' + (org.summary || '')).toLowerCase();\n  let direction = '\u2014', color = GREY;\n  if (/\\b(bought|buy|purchas|acqui)/.test(hay)) { direction = 'Buy'; color = GREEN; }\n  else if (/\\b(sold|sell|sale|dispos)/.test(hay)) { direction = 'Sell'; color = RED; }\n\n  const url = `${SITE}/news/article/${dateForUrl(org.time_published)}/${uid}/${slugify(org.title)}`;\n\n  const fields = [];\n  fields.push({ name: 'Direction', value: direction, inline: true });\n  if (org.ownership_form) { fields.push({ name: 'Holding', value: trunc(org.ownership_form, 40), inline: true }); }\n  fields.push({ name: 'Score', value: String(score) + '/10', inline: true });\n\n  const embed = {\n    title: trunc(`${tickers.join(', ')} \u2014 ${org.title || 'Insider filing'}`, 250),\n    url,\n    description: trunc(org.summary || '', 320),\n    color,\n    fields,\n    footer: { text: trunc(`insider \u00b7 ${org.source || org.source_domain || 'SEC EDGAR'} \u00b7 via AlphaAI`, 120) },\n  };\n  if (org.time_published) { embed.timestamp = org.time_published; }\n\n  const size = JSON.stringify(embed).length;\n  if (budget + size > 5200 && embeds.length > 0) { break; }\n  budget += size;\n\n  embeds.push(embed);\n  postedUids.push(uid);\n  seen.add(uid);\n}\n\nif (embeds.length === 0) { return []; }\n\nfor (const uid of postedUids) { store.postedUids.push(uid); }\nif (store.postedUids.length > 500) { store.postedUids = store.postedUids.slice(-500); }\n\nconst n = embeds.length;\nconst content = `\ud83d\udd75\ufe0f **AlphaAI \u2014 ${n} new insider ${n === 1 ? 'filing' : 'filings'}**`;\n\nreturn [{ json: { payload: { content, embeds } } }];\n"
      },
      "id": "d0000000-0000-4000-8000-0000000000d3",
      "name": "Build Discord Alert Cards",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        620,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://discord.com/api/webhooks/REPLACE_WITH_YOUR_WEBHOOK_URL",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ $json.payload }}",
        "options": {}
      },
      "id": "e0000000-0000-4000-8000-0000000000e3",
      "name": "Post Alerts to Discord",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    }
  ],
  "connections": {
    "Every 30 Minutes": {
      "main": [
        [
          {
            "node": "Fetch Insider News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Insider News": {
      "main": [
        [
          {
            "node": "Build Discord Alert Cards",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Discord Alert Cards": {
      "main": [
        [
          {
            "node": "Post Alerts to Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}