{
  "name": "Check your n8n automations: expiring credentials, execution counts and a workflow audit, reported in Slack",
  "nodes": [
    {
      "id": "47cdf2ab-1493-446e-8fe0-0f1ae06896ef",
      "name": "Every morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        -100
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 9
            }
          ]
        }
      }
    },
    {
      "id": "a60e2f51-a1e7-45e8-8e98-77dbda650a11",
      "name": "Run now (credentials)",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        100
      ],
      "parameters": {}
    },
    {
      "id": "50ef011e-0a53-47de-8565-4d9cf9aced30",
      "name": "Credential settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "warn_days",
              "value": 14,
              "type": "number"
            },
            {
              "id": "c2",
              "name": "always_report",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "c3",
              "name": "weekly_ok_day",
              "value": 1,
              "type": "number"
            },
            {
              "id": "c4",
              "name": "timezone",
              "value": "Asia/Tokyo",
              "type": "string"
            },
            {
              "id": "c5",
              "name": "sheets_test_spreadsheet_id",
              "value": "ID_OF_ANY_SPREADSHEET_YOU_CAN_OPEN",
              "type": "string"
            },
            {
              "id": "c6",
              "name": "expiry_list",
              "value": "[{\"name\":\"Example API key\",\"expires\":\"2027-01-31\",\"note\":\"rotate it in the provider console\"},{\"name\":\"Example domain\",\"expires\":\"2027-06-30\",\"note\":\"renew at the registrar\"}]",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "223d7fc1-a6b2-413e-85c4-2dc4c1693cee",
      "name": "Check Chatwork",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        600,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "https://api.chatwork.com/v2/me",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "3c54062d-3d2b-4b0f-8e06-48ad1148972d",
      "name": "Check LINE",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        820,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "https://api.line.me/v2/bot/info",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "718813dc-3c8b-4245-8ecb-2d0c1eea3d17",
      "name": "Check Slack",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1040,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://slack.com/api/auth.test",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "slackApi",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "fc0e9e06-2d2b-430f-84b6-a80d338ece25",
      "name": "Check Anthropic",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1260,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "https://api.anthropic.com/v1/models?limit=1",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "ccec5caf-8110-4c6c-85f6-9f2b0dbcca85",
      "name": "Check Gemini",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1480,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googlePalmApi",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "f3aee1b6-4181-46fe-893c-a4a8dc5b4724",
      "name": "Check Google Sheets",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1700,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $(\"Credential settings\").first().json.sheets_test_spreadsheet_id }}?fields=spreadsheetId",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "02cd6cd5-fbc9-42a6-8e0c-eb1508d8b818",
      "name": "Check Gmail",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1920,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/profile",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gmailOAuth2",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "id": "adebcef6-ca0e-453b-8e3c-55b9ec2589ea",
      "name": "Judge the credentials",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2260,
        0
      ],
      "parameters": {
        "jsCode": "const cfg = $('Credential settings').first().json;\nconst tz = cfg.timezone || 'Asia/Tokyo';\nconst now = DateTime.now().setZone(tz);\n\n// Every check node that exists in this workflow. Delete a node you do not use and it is simply skipped.\nconst CHECKS = [\n  ['Chatwork', 'Check Chatwork'], ['LINE', 'Check LINE'], ['Slack', 'Check Slack'], ['Anthropic', 'Check Anthropic'],\n  ['Gemini', 'Check Gemini'], ['Google Sheets', 'Check Google Sheets'], ['Gmail', 'Check Gmail'],\n];\n\nfunction judge(label, r) {\n  const code = Number(r?.statusCode);\n  const body = r?.body ?? {};\n  if (!code) {\n    // with \"continue on error\" the node's error message is in the item. A credential that n8n itself reports as expired shows up here.\n    const msg = String(r?.error?.message ?? r?.message ?? '').slice(0, 200);\n    if (/reconnect|expired|refresh token|invalid_grant|unauthorized/i.test(msg)) return { label, ok: false, level: 'error', reason: 'the credential must be reconnected: ' + msg };\n    return { label, ok: false, level: 'error', reason: 'no answer' + (msg ? ': ' + msg : ' (the service could not be reached, or the call timed out)') };\n  }\n  // Slack answers 200 even when the token is bad: the verdict is in the body\n  if (label === 'Slack' && code === 200 && body.ok === false) return { label, ok: false, level: 'error', reason: 'Slack says: ' + (body.error ?? 'not ok') + ' (the token is invalid or revoked)' };\n  if (code >= 200 && code < 300) return { label, ok: true, level: 'ok', reason: 'OK (' + code + ')' };\n  if (code === 401 || code === 403) return { label, ok: false, level: 'error', reason: code + ': the credential is invalid, expired or lacks permission. Reconnect or re-issue it.' };\n  if (code === 429) return { label, ok: true, level: 'warn', reason: '429: rate limited right now. The credential itself may be fine.' };\n  if (code >= 500) return { label, ok: true, level: 'warn', reason: code + ': the service has a problem on its side. Try again later.' };\n  return { label, ok: false, level: 'error', reason: code + ': unexpected answer' };\n}\n\nconst results = [];\nfor (const [label, node] of CHECKS) {\n  let r;\n  try { r = $(node).first().json; } catch (e) { continue; } // the node was deleted\n  results.push(judge(label, r));\n}\n\n// expiry dates you keep by hand: name, expires (yyyy-MM-dd), note\nlet expiries = [];\ntry { expiries = JSON.parse(cfg.expiry_list || '[]'); } catch (e) { expiries = [{ name: 'expiry_list', expires: '', note: 'could not be read as JSON' }]; }\nconst expiring = [];\nfor (const e of expiries) {\n  const d = DateTime.fromISO(String(e.expires), { zone: tz });\n  if (!d.isValid) { expiring.push({ name: e.name, days: null, level: 'warn', text: e.name + ': the date \"' + e.expires + '\" is not understood' }); continue; }\n  const days = Math.ceil(d.diff(now.startOf('day'), 'days').days);\n  if (days < 0) expiring.push({ name: e.name, days, level: 'error', text: e.name + ' EXPIRED ' + (-days) + ' days ago' + (e.note ? ' (' + e.note + ')' : '') });\n  else if (days <= Number(cfg.warn_days)) expiring.push({ name: e.name, days, level: 'warn', text: e.name + ' expires in ' + days + ' days (' + e.expires + ')' + (e.note ? ' - ' + e.note : '') });\n}\n\nconst bad = results.filter(r => !r.ok);\nconst warn = results.filter(r => r.ok && r.level === 'warn');\nconst expError = expiring.filter(e => e.level === 'error');\nconst hasProblem = bad.length > 0 || expiring.length > 0 || warn.length > 0;\nconst weeklyOk = now.weekday === Number(cfg.weekly_ok_day);\nconst shouldReport = hasProblem || !!cfg.always_report || weeklyOk;\n\nconst lines = [];\nlines.push((bad.length || expError.length ? '🚨 ' : hasProblem ? '⚠️ ' : '✅ ') + 'Credential check ' + now.toFormat('yyyy-MM-dd HH:mm'));\nfor (const r of results) lines.push((r.level === 'ok' ? '✅ ' : r.level === 'warn' ? '⚠️ ' : '❌ ') + r.label + ': ' + r.reason);\nfor (const e of expiring) lines.push((e.level === 'error' ? '❌ ' : '⏰ ') + e.text);\nif (!hasProblem) lines.push('Everything is fine.');\n\nreturn [{ json: { should_report: shouldReport, problems: bad.length, warnings: warn.length + expiring.length, checked: results.length, report: lines.join('\\n') } }];"
      }
    },
    {
      "id": "8ae43936-f915-4886-8cef-ede0240d4e16",
      "name": "Anything to report?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2480,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "r1",
              "leftValue": "={{ $json.should_report }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "450004ec-293d-4417-8bb8-15dfe8116802",
      "name": "Post the credential report",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        2700,
        -100
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ $json.report }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      }
    },
    {
      "id": "f2f9b384-ed5c-46ab-8bbd-eb12e6885aa5",
      "name": "Every Monday",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        700
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      }
    },
    {
      "id": "dcd89db1-cd4d-413e-8d44-d959cc8aab47",
      "name": "Run now (usage)",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        900
      ],
      "parameters": {}
    },
    {
      "id": "309d2d07-ccc5-4238-8abb-c2fcc792d57c",
      "name": "Usage settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        800
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "u1",
              "name": "timezone",
              "value": "Asia/Tokyo",
              "type": "string"
            },
            {
              "id": "u2",
              "name": "count_modes",
              "value": "trigger, webhook, error, retry, integrated, cli",
              "type": "string"
            },
            {
              "id": "u3",
              "name": "monthly_execution_quota",
              "value": 0,
              "type": "number"
            },
            {
              "id": "u4",
              "name": "spike_min",
              "value": 20,
              "type": "number"
            },
            {
              "id": "u5",
              "name": "failure_min",
              "value": 3,
              "type": "number"
            }
          ]
        }
      }
    },
    {
      "id": "d97a12ec-ecd9-4493-8c8d-a82364e6bd90",
      "name": "Get workflows",
      "type": "n8n-nodes-base.n8n",
      "typeVersion": 1,
      "position": [
        440,
        800
      ],
      "parameters": {
        "resource": "workflow",
        "operation": "getAll",
        "returnAll": true,
        "filters": {},
        "requestOptions": {}
      },
      "executeOnce": true
    },
    {
      "id": "5d302609-8f6a-43aa-8315-2e6cd5cac487",
      "name": "Get executions",
      "type": "n8n-nodes-base.n8n",
      "typeVersion": 1,
      "position": [
        660,
        800
      ],
      "parameters": {
        "resource": "execution",
        "operation": "getAll",
        "returnAll": true,
        "filters": {},
        "options": {},
        "requestOptions": {}
      },
      "executeOnce": true
    },
    {
      "id": "4f82447f-16de-43e3-8c74-5a8a13e4b3ae",
      "name": "Build the usage report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1000,
        800
      ],
      "parameters": {
        "jsCode": "const cfg = $('Usage settings').first().json;\nconst tz = cfg.timezone || 'Asia/Tokyo';\nconst now = DateTime.now().setZone(tz);\nconst countModes = String(cfg.count_modes || '').split(/\\s*,\\s*/).filter(Boolean);\n\n// ---- what each workflow is: name, published or not, how many AI nodes it has\nconst AI_TYPE = /anthropic|openai|gemini|lmchat|ollama|mistral|groq|cohere|agent$/i;\nconst wfs = new Map();\nfor (const it of $('Get workflows').all()) {\n  const w = it.json;\n  if (!w.id) continue;\n  const aiNodes = (w.nodes ?? []).filter(n => AI_TYPE.test(String(n.type)) && !/embedding|vectorStore|memory|outputParser/i.test(String(n.type))).length;\n  wfs.set(String(w.id), { id: String(w.id), name: w.name, active: !!w.active, archived: !!w.isArchived, nodes: (w.nodes ?? []).length, aiNodes });\n}\n\n// ---- count the executions in three windows\nconst H24 = now.minus({ hours: 24 }), D7 = now.minus({ days: 7 }), D8 = now.minus({ days: 8 }), D30 = now.minus({ days: 30 });\nconst per = new Map();\nconst slot = (id) => {\n  if (!per.has(id)) per.set(id, { id, d1: 0, d7: 0, d30: 0, prev7: 0, err1: 0, err7: 0, ai7: 0 });\n  return per.get(id);\n};\nlet oldest = null, seen = 0, skippedManual = 0;\nfor (const it of $('Get executions').all()) {\n  const e = it.json;\n  if (!e.id || !e.startedAt) continue;\n  seen++;\n  const t = DateTime.fromISO(e.startedAt, { zone: tz });\n  if (!oldest || t < oldest) oldest = t;\n  if (countModes.length && !countModes.includes(e.mode)) { skippedManual++; continue; }\n  const s = slot(String(e.workflowId));\n  const failed = e.status === 'error' || e.status === 'crashed';\n  if (t >= D30) s.d30++;\n  if (t >= D7) { s.d7++; s.ai7 += (wfs.get(String(e.workflowId))?.aiNodes ?? 0); if (failed) s.err7++; }\n  if (t >= H24) { s.d1++; if (failed) s.err1++; }\n  if (t < D7 && t >= D8.minus({ days: 6 })) s.prev7++;\n}\n// the API returns the newest executions first, up to the limit: if the oldest one we got is newer than 30 days, the 30-day number is a lower bound\nconst truncated = false; // every execution the API still holds was read (n8n prunes old ones by its own settings)\n\nconst rows = [...per.values()].map(s => ({ ...s, name: wfs.get(s.id)?.name ?? '(deleted workflow ' + s.id + ')', active: wfs.get(s.id)?.active ?? false, aiNodes: wfs.get(s.id)?.aiNodes ?? 0 }));\nrows.sort((a, b) => b.d7 - a.d7);\nconst total = (k) => rows.reduce((n, r) => n + r[k], 0);\nconst projected = Math.round(total('d7') / 7 * 30);\nconst quota = Number(cfg.monthly_execution_quota) || 0;\n\n// ---- things worth a message\nconst spikes = rows.filter(r => r.d1 >= Number(cfg.spike_min) && r.d1 > 3 * Math.max(r.prev7 / 7, 1));\nconst failing = rows.filter(r => r.err1 >= Number(cfg.failure_min));\nconst idleActive = [...wfs.values()].filter(w => w.active && !w.archived && !(per.get(w.id)?.d30 > 0));\n\nconst lines = [];\nconst bad = spikes.length || failing.length || (quota && projected > quota);\nlines.push((bad ? '⚠️ ' : '📊 ') + 'Automation usage report ' + now.toFormat('yyyy-MM-dd HH:mm'));\nlines.push('Oldest execution still stored: ' + (oldest ? oldest.toFormat('yyyy-MM-dd') : 'none') + (oldest && oldest > D30 ? ' (so the 30-day number only covers the days since then: n8n removes old execution records)' : ''));\nlines.push('Executions (modes counted: ' + (countModes.join(', ') || 'all') + '): last 24h ' + total('d1') + ', last 7 days ' + total('d7') + ', last 30 days ' + total('d30') + (truncated ? ' (at least: only the newest ' + seen + ' executions were read)' : ''));\nlines.push('At this pace: about ' + projected + ' executions a month (a projection from the last 7 days, not a measurement)' + (quota ? ' = ' + Math.round(projected / quota * 100) + '% of your ' + quota + ' quota' : ''));\nlines.push('AI calls, upper bound for the last 7 days (executions x AI nodes): ' + total('ai7'));\nlines.push('');\nlines.push('Top workflows (last 7 days):');\nfor (const r of rows.slice(0, 8)) lines.push('• ' + r.name + ': ' + r.d7 + ' (24h ' + r.d1 + ', failed ' + r.err7 + ', AI nodes ' + r.aiNodes + (r.active ? '' : ', not published') + ')');\nif (spikes.length) { lines.push(''); lines.push('📈 Sudden increase (24h is over 3x the usual day):'); for (const r of spikes) lines.push('• ' + r.name + ': ' + r.d1 + ' in 24h, usual ' + (r.prev7 / 7).toFixed(1) + ' a day'); }\nif (failing.length) { lines.push(''); lines.push('❌ Failing (' + cfg.failure_min + '+ failures in 24h):'); for (const r of failing) lines.push('• ' + r.name + ': ' + r.err1 + ' failed of ' + r.d1); }\nif (idleActive.length) { lines.push(''); lines.push('💤 Published but not run in 30 days: ' + idleActive.map(w => w.name).slice(0, 10).join(', ') + (idleActive.length > 10 ? ' ...' : '')); }\n\nreturn [{ json: { should_report: true, bad, projected, total_7d: total('d7'), report: lines.join('\\n'), rows: rows.length, executed_workflow_ids: rows.filter(r => r.d30 > 0).map(r => r.id) } }];"
      }
    },
    {
      "id": "7a4f4e62-93c0-44ef-8b4f-bc15ea88206f",
      "name": "Audit the workflows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1380,
        800
      ],
      "parameters": {
        "jsCode": "// Audit of the workflow list: facts only, each one something you may want to change.\nconst usage = $('Build the usage report').first().json;\nconst cfg = $('Usage settings').first().json;\n\nconst wfs = $('Get workflows').all().map(i => i.json).filter(w => w.id && !w.isArchived);\nconst executed30 = new Set(usage.executed_workflow_ids ?? []);\n\n// strings that look like a real secret written straight into a node (a credential should hold them instead)\nconst SECRET = [\n  [/xox[bpa]-[A-Za-z0-9-]{10,}/, 'a Slack token'],\n  [/sk-ant-[A-Za-z0-9_-]{10,}/, 'an Anthropic key'],\n  [/sk-[A-Za-z0-9]{32,}/, 'an OpenAI-style key'],\n  [/AIza[0-9A-Za-z_-]{30,}/, 'a Google API key'],\n  [/Bearer\\s+[A-Za-z0-9._~+\\/-]{30,}/, 'a Bearer token'],\n  [/ghp_[A-Za-z0-9]{30,}/, 'a GitHub token'],\n];\nconst findings = { noErrorFlow: [], openWebhook: [], secrets: [], defaultName: [], idle: [] };\nfor (const w of wfs) {\n  const nodes = w.nodes ?? [];\n  const label = w.name + (w.active ? '' : ' (not published)');\n  if (w.active && !(w.settings && w.settings.errorWorkflow)) findings.noErrorFlow.push(w.name);\n  // a published webhook without any check: anyone who finds the URL can start the workflow\n  const open = nodes.filter(n => /\\.webhook$/.test(String(n.type)) && (n.parameters?.authentication ?? 'none') === 'none');\n  if (w.active && open.length) findings.openWebhook.push(w.name + ' (' + open.map(n => n.name).join(', ') + ')');\n  const text = JSON.stringify(nodes.map(n => n.parameters));\n  for (const [re, what] of SECRET) if (re.test(text)) { findings.secrets.push(w.name + ': ' + what); break; }\n  if (/^My workflow( \\d+)?$/i.test(String(w.name))) findings.defaultName.push(w.name);\n  if (w.active && !executed30.has(String(w.id))) findings.idle.push(w.name);\n}\n\nconst total = wfs.length, published = wfs.filter(w => w.active).length;\nconst L = [];\nL.push('');\nL.push('🗂 Workflow check (' + total + ' workflows, ' + published + ' published)');\nconst section = (title, arr, hint) => { if (arr.length) { L.push(title + ' (' + arr.length + ')' + (hint ? ' - ' + hint : '')); for (const x of arr.slice(0, 8)) L.push('• ' + x); if (arr.length > 8) L.push('• ...and ' + (arr.length - 8) + ' more'); } };\nsection('🔔 Published, but no error workflow is set', findings.noErrorFlow, 'a failure here stays silent');\nsection('🌐 Published webhook with no authentication', findings.openWebhook, 'fine if the workflow checks a signature itself');\nsection('🔑 Something that looks like a secret inside a node', findings.secrets, 'move it into a credential');\nsection('💤 Published but never run in the window above', findings.idle);\nsection('🏷 Still named \"My workflow\"', findings.defaultName);\nif (!findings.noErrorFlow.length && !findings.openWebhook.length && !findings.secrets.length) L.push('Nothing to fix in the checks above.');\n\nreturn [{ json: { ...usage, report: usage.report + '\\n' + L.join('\\n'), audit: { total, published, noErrorFlow: findings.noErrorFlow.length, openWebhook: findings.openWebhook.length, secrets: findings.secrets.length, idle: findings.idle.length } } }];"
      }
    },
    {
      "id": "911b85ad-b3f5-4b44-81e8-d1233a8bd1f0",
      "name": "Post the usage report",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        1600,
        800
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ $json.report }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      }
    },
    {
      "parameters": {
        "content": "## 1. Every morning\nRuns daily at 9:00 or by hand. Set the warning period and the expiry dates you keep yourself (API keys, domains) here.",
        "height": 580,
        "width": 420,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -310
      ],
      "id": "sticky-2-1953d4ff",
      "name": "Section 1"
    },
    {
      "parameters": {
        "content": "## 2. One cheap call per service\nEach call is free and read-only. A failure is a result, not an error, so one broken credential never stops the others. Delete the services you do not use.",
        "height": 328,
        "width": 1520,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        540,
        -158
      ],
      "id": "sticky-3-edf63267",
      "name": "Section 2"
    },
    {
      "parameters": {
        "content": "## 3. Judge and report\n2xx is fine, 401 or 403 means invalid or expired, 429 and 5xx are warnings. Quiet when all is well, except one report on Mondays.",
        "height": 454,
        "width": 640,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2200,
        -284
      ],
      "id": "sticky-4-723c4f0d",
      "name": "Section 3"
    },
    {
      "parameters": {
        "content": "## 4. Every Monday: read your n8n\nReads every workflow and every stored execution through the n8n API. Set your execution quota and the limits for a sudden increase and for failures.",
        "height": 528,
        "width": 860,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        542
      ],
      "id": "sticky-5-6b50a1e9",
      "name": "Section 4"
    },
    {
      "parameters": {
        "content": "## 5. Count the executions\nCounts executions per workflow for 24 hours, 7 days and 30 days, and projects the month from the last 7 days. Manual runs are left out by default.",
        "height": 510,
        "width": 200,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        940,
        460
      ],
      "id": "sticky-6-74fa24db",
      "name": "Section 5"
    },
    {
      "parameters": {
        "content": "## 6. Audit and report\nLists published workflows with no error workflow, webhooks with no authentication, secret-looking text in nodes, and workflows that never run.",
        "height": 380,
        "width": 420,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1320,
        590
      ],
      "id": "sticky-7-97fa6510",
      "name": "Section 6"
    },
    {
      "parameters": {
        "content": "## A check-up for your n8n automations\nAutomations fail quietly: a token expires, a published workflow has no error alert, a loop doubles your execution count. This workflow looks for those things and posts a short report in Slack. It uses no AI.\n\n### How it works\n1. **Every morning**, one cheap and free call per service (Chatwork, LINE, Slack, Anthropic, Gemini, Google Sheets, Gmail) tests whether each credential still works. A 401 means invalid or expired; n8n's own \"needs to be reconnected\" is shown as it is. Dates you keep by hand (API key expiry, domain renewal) are flagged 14 days ahead. It stays quiet unless something is wrong, plus one \"all fine\" on Mondays.\n2. **Every Monday**, it reads your workflows and executions through the n8n API: executions per workflow (24 hours, 7 days, 30 days), failures, sudden increases, a monthly projection, and the most AI calls those executions could make.\n3. It audits the workflow list: published without an error workflow, published webhooks without authentication, secret-looking text inside nodes, published workflows that never run.\n\n### Setup\n1. Create an n8n API key with read-only scopes (workflow and execution: list and read) and an **n8n API** credential (Base URL `http://<your n8n>/api/v1`).\n2. In the check nodes you use, select your credentials. **Delete the checks you do not use.**\n3. Select your Slack channel in the two Slack nodes.\n4. Edit the expiry list in **Credential settings**, then publish.\n\n### Customization\nTo add a service, copy a Check node and add its name to the list in **Judge the credentials**. Set your monthly execution quota in **Usage settings**.",
        "height": 900,
        "width": 800,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -130,
        -1350
      ],
      "id": "sticky-1-f4c68e2f",
      "name": "Overview"
    }
  ],
  "connections": {
    "Every morning": {
      "main": [
        [
          {
            "node": "Credential settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run now (credentials)": {
      "main": [
        [
          {
            "node": "Credential settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Credential settings": {
      "main": [
        [
          {
            "node": "Check Chatwork",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Chatwork": {
      "main": [
        [
          {
            "node": "Check LINE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check LINE": {
      "main": [
        [
          {
            "node": "Check Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Slack": {
      "main": [
        [
          {
            "node": "Check Anthropic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Anthropic": {
      "main": [
        [
          {
            "node": "Check Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Gemini": {
      "main": [
        [
          {
            "node": "Check Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Google Sheets": {
      "main": [
        [
          {
            "node": "Check Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Gmail": {
      "main": [
        [
          {
            "node": "Judge the credentials",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Judge the credentials": {
      "main": [
        [
          {
            "node": "Anything to report?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anything to report?": {
      "main": [
        [
          {
            "node": "Post the credential report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Monday": {
      "main": [
        [
          {
            "node": "Usage settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run now (usage)": {
      "main": [
        [
          {
            "node": "Usage settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Usage settings": {
      "main": [
        [
          {
            "node": "Get workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get workflows": {
      "main": [
        [
          {
            "node": "Get executions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get executions": {
      "main": [
        [
          {
            "node": "Build the usage report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build the usage report": {
      "main": [
        [
          {
            "node": "Audit the workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Audit the workflows": {
      "main": [
        [
          {
            "node": "Post the usage report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}
