This workflow corresponds to n8n.io template #16739 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request 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": "XkylrzaXLkWull1J",
"name": "Sprint Slip Predictor",
"tags": [],
"nodes": [
{
"id": "3c04d619-60c5-4bac-8f38-894053df083b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
-80
],
"parameters": {
"width": 480,
"height": 624,
"content": "## Sprint Slip Predictor\n\n### How it works\n\nThe workflow predicts sprint slippage by fetching active sprint data from JIRA.\nIt retrieves sprint issues and computes key metrics for analysis.\nAn AI agent analyzes the metrics to predict potential sprint slippage.\nThe prediction is parsed and conditionally notifies the team via Slack if slippage is detected.\nBoth manual and scheduled triggers can initiate the workflow.\n\n### Setup steps\n\n- - [ ] Configure JIRA credentials in Config node (JIRA_DOMAIN, JIRA_BOARD_ID)\n- - [ ] Set up JIRA custom field IDs for story points and done statuses\n- - [ ] Configure OpenRouter API credentials for both AI agents\n- - [ ] Set up Slack credentials for team notifications\n\n### Customization\n\nAdjust the done statuses and story points field IDs in the Config node to match your JIRA setup. Modify the conditional logic in the If node to change notification thresholds."
},
"typeVersion": 1
},
{
"id": "d60f419e-59b4-4cf7-b3f9-6e8d6fb46359",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
-32
],
"parameters": {
"color": 7,
"width": 544,
"height": 528,
"content": "## Trigger and Configuration\n\nManual and schedule triggers initiate the workflow, feeding into configuration that sets JIRA parameters and field mappings."
},
"typeVersion": 1
},
{
"id": "d2410dc2-91b8-478d-96e5-09a689493731",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
64
],
"parameters": {
"color": 7,
"width": 416,
"height": 320,
"content": "## Fetch Sprint Data\n\nSequential HTTP requests to JIRA API - first fetches the active sprint, then retrieves all issues within that sprint."
},
"typeVersion": 1
},
{
"id": "f7d08665-5961-4692-bc86-166373b0ba7d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1872,
96
],
"parameters": {
"color": 7,
"width": 800,
"height": 480,
"content": "## Process and Predict\n\nComputes sprint metrics from fetched data, uses AI to predict slippage, then parses the prediction code for evaluation."
},
"typeVersion": 1
},
{
"id": "90a257fa-a0b1-46e4-a48e-d301ed0e782b",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2752,
-128
],
"parameters": {
"color": 7,
"width": 704,
"height": 736,
"content": "## Output and Notification\n\nConditional branching determines if team notification is needed, with Slack integration for alerting."
},
"typeVersion": 1
},
{
"id": "00f54e17-fe76-47b6-89fd-8e1b0e9ebb87",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
784,
128
],
"parameters": {},
"typeVersion": 1
},
{
"id": "6cb16eda-c41e-4f34-819d-2ed53769606b",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
784,
336
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "04d77e01-ff99-4ae9-86b6-b8a658d7f0c3",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
1136,
224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "66ba8646-257c-4a8e-ae6f-953898e3ab68",
"name": "JIRA_DOMAIN",
"type": "string",
"value": "your-instance.atlassian.net"
},
{
"id": "13cde6fa-41f8-42de-8a73-e2b4a2f57b98",
"name": "JIRA_BOARD_ID",
"type": "number",
"value": 35
},
{
"id": "3f53f1c1-c5a8-4faf-ab08-e6ef8945b77d",
"name": "STORY_POINTS_FIELD",
"type": "string",
"value": "customfield_10016"
},
{
"id": "483fc5b8-2f3a-4905-964c-b87d0f824d22",
"name": "DONE_STATUSES",
"type": "string",
"value": "DONE"
},
{
"id": "2f965d1c-83bb-4637-9efe-9e933bc6e9ad",
"name": "WIP_STATUSES",
"type": "string",
"value": "TO DO, IN PROGRESS, TO REVIEW"
},
{
"id": "4cded48b-032f-490d-8e9d-2a39a79e1965",
"name": "RISK_THRESHOLD",
"type": "string",
"value": "at_risk"
},
{
"id": "5dd3a49d-fe24-4507-a763-0a872ae64eba",
"name": "SLACK_CHANNEL",
"type": "string",
"value": "#sprint-alerts"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "77ae8a7f-c9d5-4602-bd9a-e9ac0dce1376",
"name": "Fetch Active Sprint",
"type": "n8n-nodes-base.httpRequest",
"position": [
1440,
224
],
"parameters": {
"url": "=https://{{ $('Config').item.json.JIRA_DOMAIN }}/rest/agile/1.0/board/{{ $('Config').item.json.JIRA_BOARD_ID }}/sprint?state=active",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "jiraSoftwareCloudApi"
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "d1102508-2492-4d65-b1e6-2d0021e101ea",
"name": "Parse Prediction Code",
"type": "n8n-nodes-base.code",
"position": [
2528,
224
],
"parameters": {
"jsCode": "const response = $input.first().json.output ?? $input.first().json.text ?? \"\";\n\nconst cleaned = response\n .replace(/^```json\\s*/i, \"\")\n .replace(/^```\\s*/i, \"\")\n .replace(/\\s*```$/i, \"\")\n .trim();\n\ntry {\n const parsed = JSON.parse(cleaned);\n // Merge prediction with the original metrics for downstream use\n const metrics = $('Compute Sprint Metrics Code').first().json;\n return [{\n json: {\n ...parsed,\n sprint_name: metrics.sprint_name,\n completion_pct: metrics.completion_pct,\n remaining_days: metrics.remaining_days,\n total_points: metrics.total_points,\n done_points: metrics.done_points\n }\n }];\n} catch (error) {\n // Non-blocking fallback \u2014 no alert on parse failure rather than crash\n return [{\n json: {\n risk_level: \"on_track\",\n risk_score: 0,\n headline: \"Prediction parse failed \u2014 no alert sent\",\n diagnosis: `Invalid JSON returned by Slip Predictor Agent: ${error.message}`,\n recommendations: [],\n tickets_to_watch: [],\n error: true\n }\n }];\n}"
},
"typeVersion": 2
},
{
"id": "ad5d9e2a-89a2-41b2-a99c-643a7830bac3",
"name": "Compute Sprint Metrics Code",
"type": "n8n-nodes-base.code",
"position": [
1920,
224
],
"parameters": {
"jsCode": "// --- Inputs ---\nconst issues = $input.first().json.issues;\nconst config = $('Config').first().json;\nconst sprint = $('Fetch Active Sprint').first().json.values[0];\n\n// --- Sprint timing ---\nconst sprintStart = new Date(sprint.startDate);\nconst sprintEnd = new Date(sprint.endDate);\nconst now = new Date();\n\nconst totalMs = Math.max(1, sprintEnd - sprintStart);\nconst elapsedMs = Math.min(totalMs, Math.max(0, now - sprintStart));\nconst remainingMs = Math.max(0, sprintEnd - now);\n\nconst totalDays = totalMs / 86400000;\nconst remainingDays = remainingMs / 86400000;\nconst elapsedDays = elapsedMs / 86400000;\nconst timeElapsedPct = (elapsedMs / totalMs) * 100;\n\n// --- Points by status category (universal keys) ---\nconst spField = config.STORY_POINTS_FIELD;\nlet totalPoints = 0, donePoints = 0, inProgressPoints = 0, todoPoints = 0;\nconst blockers = [];\n\nfor (const issue of issues) {\n const pts = issue.fields?.[spField] ?? 0;\n const catKey = issue.fields?.status?.statusCategory?.key ?? \"new\";\n const labels = issue.fields?.labels ?? [];\n totalPoints += pts;\n\n if (catKey === \"done\") donePoints += pts;\n else if (catKey === \"indeterminate\") inProgressPoints += pts;\n else todoPoints += pts;\n\n // Flag blocked tickets (label or status name containing \"block\")\n const statusName = (issue.fields?.status?.name ?? \"\").toLowerCase();\n if (labels.some(l => l.toLowerCase().includes(\"block\")) || statusName.includes(\"block\")) {\n blockers.push({ key: issue.key, summary: issue.fields?.summary, points: pts });\n }\n}\n\n// --- Core slip signals ---\nconst completionPct = totalPoints > 0 ? (donePoints / totalPoints) * 100 : 0;\nconst idealCompletionPct = timeElapsedPct;\nconst slipMargin = completionPct - idealCompletionPct; // negative = behind\n\nconst remainingPoints = totalPoints - donePoints;\nconst requiredPointsPerDay = remainingDays > 0 ? remainingPoints / remainingDays : remainingPoints;\nconst actualPointsPerDay = elapsedDays > 0 ? donePoints / elapsedDays : 0;\nconst velocityGap = actualPointsPerDay - requiredPointsPerDay; // negative = too slow\n\nreturn [{\n json: {\n sprint_name: sprint.name,\n sprint_goal: sprint.goal || \"(no goal set)\",\n total_days: Math.round(totalDays * 10) / 10,\n remaining_days: Math.round(remainingDays * 10) / 10,\n time_elapsed_pct: Math.round(timeElapsedPct),\n total_points: totalPoints,\n done_points: donePoints,\n in_progress_points: inProgressPoints,\n todo_points: todoPoints,\n completion_pct: Math.round(completionPct),\n ideal_completion_pct: Math.round(idealCompletionPct),\n slip_margin: Math.round(slipMargin),\n remaining_points: remainingPoints,\n required_points_per_day: Math.round(requiredPointsPerDay * 100) / 100,\n actual_points_per_day: Math.round(actualPointsPerDay * 100) / 100,\n velocity_gap: Math.round(velocityGap * 100) / 100,\n blocker_count: blockers.length,\n blockers: blockers\n }\n}];"
},
"typeVersion": 2
},
{
"id": "b9a3dfed-1421-4304-80a3-74085732c9e0",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
2800,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "bfc3386d-a3e8-4910-8f40-b0491c57b281",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.risk_level }}",
"rightValue": "on_track"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "4a650a8a-49fc-43de-9b1a-d7571d7190b8",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
3056,
432
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c2350522-9453-4a6a-a80d-2ab40c69151d",
"name": "Send a message in Slack",
"type": "n8n-nodes-base.slackTool",
"position": [
3328,
192
],
"parameters": {
"text": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message_Text', ``, 'string') }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "={{ $('Config').item.json.SLACK_CHANNEL }}"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "6af03ddb-5793-466f-85aa-5eb0dcc2a68a",
"name": "Team Notifier Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
3088,
0
],
"parameters": {
"text": "=Compose and post a motivating team message about this sprint's status.\n\n<sprint_status>\nSprint: {{ $json.sprint_name }}\nRisk level: {{ $json.risk_level }} ({{ $json.risk_score }}/10)\nCompletion: {{ $json.completion_pct }}% ({{ $json.done_points }}/{{ $json.total_points }} pts)\nRemaining: {{ $json.total_points - $json.done_points }} pts to do, {{ $json.remaining_days }} day(s) left\n\nAnalyst headline: {{ $json.headline }}\nAnalyst diagnosis: {{ $json.diagnosis }}\nRecommended actions:\n{{ $json.recommendations.map((r, i) => `${i + 1}. ${r}`).join('\\n') }}\n</sprint_status>\n\nPost the message to the team channel now.",
"options": {
"systemMessage": "You are a team-focused engineering manager.\n\nYOUR PRIMARY TASK: compose a motivating Slack message about a sprint that is \nfalling behind, then IMMEDIATELY post it to the team channel by calling the \nSlack tool. Posting the message is mandatory \u2014 a message that is written but \nnot posted is a failure. You MUST call the Slack tool exactly once.\n\nOnce posted, reply with a one-line confirmation of what you sent.\n\n---\n\nBelow are the rules for HOW to write the message.\n\nTone rules:\n- Warm, collective, energizing. Use \"we\"/\"nous\", never \"you\" as blame.\n- Frame the delay as a shared, recoverable challenge \u2014 never a failure.\n- Honest about the situation but always end on a confident note.\n- Write in the same language as the sprint data (French if tickets are French).\n- Reframe the analyst's recommendations as opportunities, not orders.\n\nCRITICAL Slack formatting rules:\n- Slack uses SINGLE asterisks for bold: *like this*. NEVER use double asterisks **like this** \u2014 they render literally and look broken.\n- Separate every logical block with a BLANK LINE for readability.\n- Use a clear visual structure with section emojis as headers, like:\n\n:rocket: *[Punchy headline]*\n\n[1-2 sentence framing of the situation]\n\n:dart: *Notre plan d'action*\n1. [action]\n2. [action]\n3. [action]\n\n:bar_chart: *O\u00f9 on en est*\n[completion snapshot in one line]\n\n:fire: *Prochaine \u00e9tape*\n[concrete next step \u2014 usually the next stand-up]\n\n[One short motivating closing line]\n\n- Keep each line concise. Avoid dense paragraphs.\n- Use natural domain terms: say \"le sprint\" or \"le board Jira\", never \"le tableau\".\n- Do not invent metrics. Use only the numbers provided.\n\nProcess:\n1. Read the sprint status.\n2. Compose the message following the structure above.\n3. Call the \"Send a message in Slack\" tool ONCE to post it.\n4. Respond with a brief confirmation.\n\nDo not post more than one message."
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "632c4d88-dee1-4128-9a0e-bd90125be6e6",
"name": "Slip Predictor Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2208,
224
],
"parameters": {
"text": "==Analyze this sprint's health and predict slip risk.\n\n<sprint_metrics>\nSprint: {{ $json.sprint_name }}\nGoal: {{ $json.sprint_goal }}\nDays remaining: {{ $json.remaining_days }} of {{ $json.total_days }}\nTime elapsed: {{ $json.time_elapsed_pct }}%\n\nCompletion: {{ $json.completion_pct }}% done ({{ $json.done_points }}/{{ $json.total_points }} pts)\nIdeal completion at this point: {{ $json.ideal_completion_pct }}%\nSlip margin: {{ $json.slip_margin }} points\n\nWork distribution:\n- Done: {{ $json.done_points }} pts\n- In progress: {{ $json.in_progress_points }} pts\n- To do: {{ $json.todo_points }} pts\n\nVelocity:\n- Required from now: {{ $json.required_points_per_day }} pts/day\n- Actual so far: {{ $json.actual_points_per_day }} pts/day\n- Gap: {{ $json.velocity_gap }} pts/day\n\nBlockers: {{ $json.blocker_count }}\n{{ JSON.stringify($json.blockers) }}\n</sprint_metrics>\n\nReturn the risk assessment JSON.",
"options": {
"systemMessage": "You are a sprint risk analyst. You receive computed metrics about an active \nsprint and must assess the risk that the sprint will not complete on time.\n\nYou do NOT recalculate the numbers \u2014 they are already computed and reliable. \nYour job is to interpret them, assign a risk level, and recommend action.\n\nKey signals to weigh:\n- slip_margin: completion % minus ideal % based on time elapsed. Negative = behind.\n- velocity_gap: actual points/day minus required points/day. Negative = too slow.\n- remaining_points vs remaining_days: is the remaining work realistic?\n- blocker_count: blocked tickets are a leading indicator of slip.\n- Early sprint (time_elapsed_pct < 30) \u2192 be lenient, small gaps are normal.\n- Late sprint (time_elapsed_pct > 70) \u2192 be strict, gaps are hard to recover.\n\nAssign one risk level:\n- \"on_track\" \u2014 sprint will likely complete; no action needed\n- \"at_risk\" \u2014 recoverable slip; PM should act now\n- \"critical\" \u2014 sprint will likely miss its goal without intervention\n\nOutput a JSON object with this exact structure:\n{\n \"risk_level\": \"on_track\" | \"at_risk\" | \"critical\",\n \"risk_score\": integer 1 to 10,\n \"headline\": \"one-line summary of the sprint health\",\n \"diagnosis\": \"2-3 sentences explaining WHY, citing the specific metrics\",\n \"recommendations\": [\"2-4 concrete, prioritized actions the PM should take\"],\n \"tickets_to_watch\": [\"keys of the most at-risk or blocking tickets\"]\n}\n\nBe direct and specific. Cite real numbers from the metrics. \nFrame recommendations as PM actions (rescope, reassign, unblock, escalate), \nnot vague advice. Return only the JSON. No preamble, no markdown fences."
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "1a9757c6-31f1-44e7-871e-6df1bf60ed51",
"name": "Fetch Sprint Issues",
"type": "n8n-nodes-base.httpRequest",
"position": [
1664,
224
],
"parameters": {
"url": "=https://{{ $('Config').item.json.JIRA_DOMAIN }}/rest/agile/1.0/sprint/{{ $json.values[0].id }}/issue?fields=summary,status,labels,{{ $('Config').item.json.STORY_POINTS_FIELD }}&maxResults=100",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "jiraSoftwareCloudApi"
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "0a3fbf05-ec93-427f-81c6-91fb4ab73630",
"name": "OpenAI Slip Predictor",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
2144,
416
],
"parameters": {
"model": "openai/gpt-oss-20b:free",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "91b0116f-2ae4-407b-b205-04c5cb6b434e",
"name": "OpenAI Team Notifier",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
3040,
208
],
"parameters": {
"model": "openai/gpt-oss-20b:free",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "42c6a5c6-a76d-4a5f-8923-800aea64e12d",
"connections": {
"If": {
"main": [
[
{
"node": "Team Notifier Agent",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Fetch Active Sprint",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Fetch Active Sprint": {
"main": [
[
{
"node": "Fetch Sprint Issues",
"type": "main",
"index": 0
}
]
]
},
"Fetch Sprint Issues": {
"main": [
[
{
"node": "Compute Sprint Metrics Code",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Team Notifier": {
"ai_languageModel": [
[
{
"node": "Team Notifier Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Slip Predictor Agent": {
"main": [
[
{
"node": "Parse Prediction Code",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Slip Predictor": {
"ai_languageModel": [
[
{
"node": "Slip Predictor Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Parse Prediction Code": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Send a message in Slack": {
"ai_tool": [
[
{
"node": "Team Notifier Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Compute Sprint Metrics Code": {
"main": [
[
{
"node": "Slip Predictor Agent",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Config",
"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.
jiraSoftwareCloudApiopenRouterApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs on a weekday morning schedule (or manually) to pull the active Jira sprint and its issues, compute sprint health metrics, use an OpenRouter-hosted OpenAI model to assess slip risk, and post a motivating alert to a Slack channel when the sprint is behind. Runs…
Source: https://n8n.io/workflows/16739/ — 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.
The AI-Powered Shopify SEO Content Automation is an enterprise-grade workflow that transforms product content creation for e-commerce stores. This sophisticated multi-agent system integrates GPT-4o, C
Arvifund - Supabase (Fixed v6). Uses httpRequest, telegram, supabase, telegramTrigger. Event-driven trigger; 92 nodes.
Arvifund - Supabase (Fixed v5). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 91 nodes.
Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.