AutomationFlowsSlack & Telegram › Monitor Github Actions Budget and Send Usage Alerts to Slack

Monitor Github Actions Budget and Send Usage Alerts to Slack

ByAdriaan van Niekerk @adriaan on n8n.io

This workflow runs daily to track GitHub Actions usage across selected repositories, combines recent workflow-run stats with GitHub Actions billing data, calculates budget burn rate and projected spend, and posts a summary to Slack with an additional alert when usage exceeds a…

Cron / scheduled trigger★★★★☆ complexity18 nodesHTTP RequestSlack
Slack & Telegram Trigger: Cron / scheduled Nodes: 18 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16167 — we link there as the canonical source.

This workflow follows the HTTP Request → Slack 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 →

Download .json
{
  "id": "4728tv9kqR5xbk8t",
  "name": "Monitor GitHub Actions Usage and Prevent Budget Overruns with Slack Alerts",
  "tags": [],
  "nodes": [
    {
      "id": "6a1b5a6d-2eed-4305-bfa8-2f70c6cbcbb1",
      "name": "Main \u2014 GitHub Actions Usage Monitor",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1248,
        32
      ],
      "parameters": {
        "width": 360,
        "height": 640,
        "content": "## \ud83d\udcca GitHub Actions Usage & Cost Monitor\n\n### \ud83d\udc64 Who's it for\nPlatform engineers, DevOps leads, and FinOps teams who pay for GitHub Actions minutes and want a daily health-check before the bill arrives.\n\n### \u2699\ufe0f What it does\n1. Runs daily via schedule.\n2. Reads your repo list from config.\n3. Fetches recent workflow runs aggregated by workflow name.\n4. Pulls org-level billing data from GitHub's Actions billing API.\n5. Calculates budget burn rate: % of monthly budget, projected full-month spend, estimated cost.\n6. Posts a daily summary to Slack.\n7. If usage exceeds your threshold %, sends an urgent budget alert.\n\n### \ud83d\udee0\ufe0f How to set up\n\u2022 Attach **GitHub** (OAuth2 or PAT with `admin:org` read) and **Slack** credentials.\n\u2022 Edit **Configuration**: repo list, monthly budget, alert threshold, cost per minute.\n\u2022 For personal accounts, swap the billing API URL to `/users/{user}/settings/billing/actions`."
      },
      "typeVersion": 1
    },
    {
      "id": "dd405d1f-72b3-4111-bbb1-a4710dbf8872",
      "name": "Section \u2014 Collect",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        336
      ],
      "parameters": {
        "width": 916,
        "height": 464,
        "content": "## \ud83d\udce5 **Collect**"
      },
      "typeVersion": 1
    },
    {
      "id": "30e6cb52-0094-4397-832b-b0059151c88a",
      "name": "Section \u2014 Analyze & Calculate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        336
      ],
      "parameters": {
        "width": 632,
        "height": 464,
        "content": "## \ud83d\udcc8 **Analyze & Calculate**"
      },
      "typeVersion": 1
    },
    {
      "id": "008742ae-b5d8-44d1-9caa-cd0d8cbec2a6",
      "name": "Section \u2014 Report & Alert",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        336
      ],
      "parameters": {
        "width": 692,
        "height": 464,
        "content": "## \ud83d\udcac **Report & Alert**"
      },
      "typeVersion": 1
    },
    {
      "id": "11a1de92-2836-4138-ac92-67a439bb87c4",
      "name": "Credentials Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1248,
        688
      ],
      "parameters": {
        "width": 360,
        "height": 100,
        "content": "\ud83d\udd11 **Credentials needed**\n\u2022 GitHub token: `repo` + `admin:org` (read)\n\u2022 Slack Bot (`chat:write`)"
      },
      "typeVersion": 1
    },
    {
      "id": "9f04cbff-034d-4922-8209-08484f5b8687",
      "name": "Personal Account Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        656
      ],
      "parameters": {
        "color": 5,
        "width": 220,
        "height": 128,
        "content": "\u2139\ufe0f **Personal accounts**\nEdit the `Fetch Org Billing Data` node URL to:\n`/users/{{owner}}/settings/billing/actions`"
      },
      "typeVersion": 1
    },
    {
      "id": "54faec29-5758-4afe-aded-4ae740ec2a85",
      "name": "Daily Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -800,
        496
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 23
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "d5281acd-12dd-46c4-975e-6c4050e019c0",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -576,
        496
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "repoOwner",
              "name": "repoOwner",
              "type": "string",
              "value": "your-org-or-username"
            },
            {
              "id": "monitorRepos",
              "name": "monitorRepos",
              "type": "string",
              "value": "repo-one,repo-two,repo-three"
            },
            {
              "id": "budgetMinutesPerMonth",
              "name": "budgetMinutesPerMonth",
              "type": "number",
              "value": 2000
            },
            {
              "id": "alertThresholdPercent",
              "name": "alertThresholdPercent",
              "type": "number",
              "value": 80
            },
            {
              "id": "slackChannel",
              "name": "slackChannel",
              "type": "string",
              "value": "#devops-finops"
            },
            {
              "id": "costPerMinute",
              "name": "costPerMinute",
              "type": "number",
              "value": 0.008
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "52a25cfb-67a2-4741-aea9-931906a3e948",
      "name": "Expand Repo List",
      "type": "n8n-nodes-base.code",
      "position": [
        -352,
        496
      ],
      "parameters": {
        "jsCode": "const config = $('Configuration').item.json;\nconst repos = config.monitorRepos.split(',').map(r => r.trim());\nreturn repos.map(repo => ({ repoName: repo, repoOwner: config.repoOwner }));"
      },
      "typeVersion": 2
    },
    {
      "id": "0d6731ab-c702-4fb9-af22-31e132afc6c7",
      "name": "Fetch Recent Workflow Runs",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -128,
        496
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/actions/runs?per_page=100&created=>{{ new Date(Date.now() - 24*3600*1000).toISOString() }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        },
        "githubOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "07210c31-686e-4967-b2b4-a0ef922b1581",
      "name": "Aggregate Workflow Stats",
      "type": "n8n-nodes-base.code",
      "position": [
        96,
        496
      ],
      "parameters": {
        "jsCode": "const allStats = {};\nconst result = {\n  reposScanned: 0,\n  totalRuns: 0,\n  totalCompleted: 0,\n  totalFailed: 0,\n  workflowStats: []\n};\n\nfor (const item of $input.all()) {\n  result.reposScanned++;\n  const runs = item.json.workflow_runs || [];\n  for (const run of runs) {\n    const wfName = run.name || run.workflow_id || 'unknown';\n    if (!allStats[wfName]) {\n      allStats[wfName] = {\n        workflowName: wfName,\n        totalRuns: 0,\n        completedRuns: 0,\n        failedRuns: 0\n      };\n    }\n    const entry = allStats[wfName];\n    entry.totalRuns++;\n    result.totalRuns++;\n    if (run.status === 'completed') {\n      entry.completedRuns++;\n      result.totalCompleted++;\n      if (run.conclusion === 'failure') {\n        entry.failedRuns++;\n        result.totalFailed++;\n      }\n    }\n  }\n}\n\nresult.workflowStats = Object.values(allStats);\nreturn result;"
      },
      "typeVersion": 2
    },
    {
      "id": "5de1175a-9c6e-441f-a389-61c15ef7b2b6",
      "name": "Fetch Org Billing Data",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        320,
        496
      ],
      "parameters": {
        "url": "=https://api.github.com/orgs/{{ $('Configuration').item.json.repoOwner }}/settings/billing/actions",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubOAuth2Api"
      },
      "credentials": {
        "githubOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4,
      "alwaysOutputData": false
    },
    {
      "id": "7efb319a-b0bf-4b4b-a1be-d8d437d3470e",
      "name": "Calculate Budget Burn Rate",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        496
      ],
      "parameters": {
        "jsCode": "const billing = $input.first().json;\nconst config = $('Configuration').item.json;\n\nconst totalMinutesUsed = billing.total_minutes_used_minutes || 0;\nconst includableMinutes = billing.included_minutes || 0;\nconst billableMinutes = totalMinutesUsed > includableMinutes ? (totalMinutesUsed - includableMinutes) : 0;\nconst estimatedCost = billableMinutes * config.costPerMinute;\nconst budgetLimit = config.budgetMinutesPerMonth;\n\nconst now = new Date();\nconst dayOfMonth = now.getDate();\nconst daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\nconst monthFraction = dayOfMonth / daysInMonth;\n\nconst expectedFullMonth = totalMinutesUsed / monthFraction;\nconst percentOfBudget = (totalMinutesUsed / budgetLimit) * 100;\nconst isOverBudget = percentOfBudget >= config.alertThresholdPercent;\nconst isOnTrackToExceed = expectedFullMonth > budgetLimit;\n\nreturn {\n  reportDate: now.toISOString().split('T')[0],\n  totalMinutesUsed,\n  includableMinutes,\n  billableMinutes,\n  estimatedCost,\n  budgetLimit,\n  percentOfBudget: Math.round(percentOfBudget * 10) / 10,\n  expectedFullMonth: Math.round(expectedFullMonth),\n  isOverBudget,\n  isOnTrackToExceed,\n  dayOfMonth,\n  daysInMonth\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3f56deb9-4915-4f18-a037-78b162ca99a4",
      "name": "Format Usage Report",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        496
      ],
      "parameters": {
        "jsCode": "const billing = $('Calculate Budget Burn Rate').item.json;\nconst stats = $('Aggregate Workflow Stats').item.json;\n\nlet emoji = billing.isOverBudget ? '\ud83d\udd34' : billing.isOnTrackToExceed ? '\ud83d\udfe1' : '\ud83d\udfe2';\n\nlet message = `*\ud83d\udcca Daily GitHub Actions Usage Report \u2014 ${billing.reportDate}*\\n\\n` +\n  `${emoji} *Budget Status:* ${billing.percentOfBudget}% of monthly budget\\n` +\n  `\u2022 Minutes used: *${billing.totalMinutesUsed}* / ${billing.budgetLimit}\\n` +\n  `\u2022 Included minutes: ${billing.includableMinutes}\\n` +\n  `\u2022 Billable minutes: ${billing.billableMinutes}\\n` +\n  `\u2022 Est. cost: $${billing.estimatedCost.toFixed(2)}\\n` +\n  `\u2022 Projected full month: *${billing.expectedFullMonth}* min\\n\\n`;\n\nmessage += `*\u26a1 Recent Workflow Runs (last 5 days)*\\n`;\nmessage += `\u2022 Repos scanned: ${stats.reposScanned}\\n`;\nmessage += `\u2022 Total runs: ${stats.totalRuns}\\n`;\nmessage += `\u2022 Completed: ${stats.totalCompleted} | Failed: ${stats.totalFailed}\\n`;\n\nif (stats.workflowStats && stats.workflowStats.length > 0) {\n  message += `\\n*Workflow breakdown:*\\n`;\n  const top = stats.workflowStats.sort((a,b)=>b.totalRuns-a.totalRuns).slice(0,10);\n  for (const wf of top) {\n    const status = wf.failedRuns > 0 ? `\u26a0\ufe0f ${wf.failedRuns} failed` : '\u2705';\n    message += `\u2022 ${wf.workflowName}: ${wf.totalRuns} runs (${status})\\n`;\n  }\n}\n\nmessage += `\\n_Day ${billing.dayOfMonth} of ${billing.daysInMonth}_`;\n\nreturn { message };"
      },
      "typeVersion": 2
    },
    {
      "id": "9d436d6d-e4d7-4049-8584-f744878ca0c5",
      "name": "Post Daily Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1216,
        592
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "761509bf-a88b-4828-b7e6-2fbe41e76c2d",
      "name": "Budget Exceeded Threshold?",
      "type": "n8n-nodes-base.if",
      "position": [
        992,
        496
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "over-budget",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $('Calculate Budget Burn Rate').item.json.isOverBudget }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "711b0b43-611f-43a8-8a10-b97fba64c860",
      "name": "Send Urgent Budget Warning",
      "type": "n8n-nodes-base.slack",
      "position": [
        1216,
        400
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *ACTIONS BUDGET ALERT*\\n\\nGitHub Actions has used *{{ $('Calculate Budget Burn Rate').item.json.percentOfBudget }}%* of the monthly budget!\\n\\n\u2022 Used: {{ $('Calculate Budget Burn Rate').item.json.totalMinutesUsed }} min\\n\u2022 Budget: {{ $('Calculate Budget Burn Rate').item.json.budgetLimit }} min\\n\u2022 Est. cost: ${{ $('Calculate Budget Burn Rate').item.json.estimatedCost.toFixed(2) }}\\n\\n\u26a0\ufe0f Action needed \u2014 review usage to avoid overage!",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "ae8b6247-6795-447e-8e75-3a585314e493",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        656
      ],
      "parameters": {
        "color": 5,
        "height": 128,
        "content": "\u26a0\ufe0f**Error Path**\nWhen a repo has not actions for the given timeframe, it exists via the Error path\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a0fa0452-62f3-40b2-a7b2-0845fe811a9c",
  "nodeGroups": [],
  "connections": {
    "Configuration": {
      "main": [
        [
          {
            "node": "Expand Repo List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand Repo List": {
      "main": [
        [
          {
            "node": "Fetch Recent Workflow Runs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Usage Report": {
      "main": [
        [
          {
            "node": "Budget Exceeded Threshold?",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Daily Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Org Billing Data": {
      "main": [
        [
          {
            "node": "Calculate Budget Burn Rate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Workflow Stats": {
      "main": [
        [
          {
            "node": "Fetch Org Billing Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Budget Exceeded Threshold?": {
      "main": [
        [
          {
            "node": "Send Urgent Budget Warning",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post Daily Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Budget Burn Rate": {
      "main": [
        [
          {
            "node": "Format Usage Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Recent Workflow Runs": {
      "main": [
        [
          {
            "node": "Aggregate Workflow Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs daily to track GitHub Actions usage across selected repositories, combines recent workflow-run stats with GitHub Actions billing data, calculates budget burn rate and projected spend, and posts a summary to Slack with an additional alert when usage exceeds a…

Source: https://n8n.io/workflows/16167/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

This workflow is designed for engineering teams, project managers, and IT operations who need consistent visibility into team availability across multiple projects. It’s perfect for organizations that

HTTP Request, Execute Workflow Trigger, Slack
Slack & Telegram

This workflow is an automated system that tracks End-of-Life (EOL) dates for software and technologies used across your projects. It eliminates the need to manually monitor EOL dates in spreadsheets o

HTTP Request, Noco Db, Slack
Slack & Telegram

This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of

HTTP Request, Telegram, Google Sheets +1
Slack & Telegram

Enhance financial oversight with this automated n8n workflow. Triggered every 5 minutes, it fetches real-time bank transactions via an API, enriches and transforms the data, and applies smart logic to

HTTP Request, Email Send, Google Sheets +1
Slack & Telegram

This workflow automates competitive price intelligence using Bright Data's enterprise web scraping API. On a scheduled basis (default: daily at 9 AM), the system loops through configured competitor pr

HTTP Request, Google Sheets, Slack +1