{
  "name": "Concept Approved \u2014 Fan-out to Work Items",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "taskade/concept-approved",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-in",
      "name": "Webhook: concept-approved",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "notes": "Invoke with: POST {WEBHOOK_URL}webhook/taskade/concept-approved\nBody JSON: {\"projectId\":\"...\", \"conceptName\":\"...\", \"clientName\":\"...\"}\nThe projectId is the Taskade CONCEPT project (copy of template Hm1fznBMzqwFkcov)."
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "projectId",
              "value": "={{$json.body.projectId}}"
            },
            {
              "name": "conceptName",
              "value": "={{$json.body.conceptName}}"
            },
            {
              "name": "clientName",
              "value": "={{$json.body.clientName}}"
            },
            {
              "name": "workItemsProjectId",
              "value": "={{$env.TASKADE_WORK_ITEMS_PROJECT_ID}}"
            }
          ]
        },
        "options": {}
      },
      "id": "set-vars",
      "name": "Extract payload",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://www.taskade.com/api/v1/projects/{{$json.projectId}}/tasks",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env.TASKADE_API_KEY}}"
            },
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "fetch-tasks",
      "name": "Taskade: list concept tasks",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        300
      ],
      "notes": "Reads the Branch 4 Channel list from the concept project (tasks with channel names like Instagram Reel, TikTok, Email, etc)."
    },
    {
      "parameters": {
        "jsCode": "// Extract Branch-4 channel rows from Taskade task list.\n// Taskade V1 returns tasks in document order; we filter to rows whose\n// text starts with a channel emoji/label.  Adjust the regex if your template\n// changes \u2014 current Concept Template Hm1fznBMzqwFkcov labels channels\n// under a 'Channels' section.\nconst rows = $input.first().json.items || $input.first().json || [];\nconst channelRx = /^(Instagram|TikTok|YouTube|LinkedIn|Email|Newsletter|X\\b|Threads|Blog|Podcast|OOH|Paid|Ad)/i;\nconst channels = rows\n  .filter(r => r && typeof r.text === 'string' && channelRx.test(r.text.trim()))\n  .map(r => ({ name: r.text.trim(), sourceTaskId: r.id }));\n\nif (channels.length === 0) {\n  // Fallback: if no channel rows are detected, emit a single 'Default' work item.\n  channels.push({ name: 'Default', sourceTaskId: null });\n}\nreturn channels.map(c => ({ json: c }));"
      },
      "id": "extract-channels",
      "name": "Parse channels",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://www.taskade.com/api/v1/projects/{{$('Extract payload').first().json.workItemsProjectId}}/tasks",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env.TASKADE_API_KEY}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"tasks\": [\n    {\n      \"content\": \"[{{$json.name}}] {{$('Extract payload').first().json.conceptName}}\",\n      \"placement\": \"afterbegin\"\n    }\n  ]\n}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "create-work-item",
      "name": "Taskade: create Work Item",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "url": "http://langfuse:3000/api/public/ingestion",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Basic {{Buffer.from($env.LANGFUSE_PUBLIC_KEY + ':' + $env.LANGFUSE_SECRET_KEY).toString('base64')}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"batch\": [\n    {\n      \"id\": \"{{$now.toISO()}}-{{$('Extract payload').first().json.projectId}}\",\n      \"timestamp\": \"{{$now.toISO()}}\",\n      \"type\": \"trace-create\",\n      \"body\": {\n        \"id\": \"concept-fanout-{{$('Extract payload').first().json.projectId}}\",\n        \"name\": \"concept-approved-fanout\",\n        \"userId\": \"{{$('Extract payload').first().json.clientName}}\",\n        \"metadata\": {\n          \"conceptName\": \"{{$('Extract payload').first().json.conceptName}}\",\n          \"workItemsCreated\": \"{{$items().length}}\"\n        }\n      }\n    }\n  ]\n}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json",
              "neverError": true
            }
          }
        }
      },
      "id": "langfuse-trace",
      "name": "Langfuse: trace fan-out",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1340,
        300
      ],
      "continueOnFail": true,
      "notes": "Fire-and-forget tracing. Requires LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY in .env after first Langfuse login."
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"ok\": true,\n  \"conceptName\": \"{{$('Extract payload').first().json.conceptName}}\",\n  \"workItemsCreated\": \"{{$('Parse channels').all().length}}\"\n}",
        "options": {}
      },
      "id": "respond",
      "name": "Respond to Taskade",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1560,
        300
      ]
    }
  ],
  "connections": {
    "Webhook: concept-approved": {
      "main": [
        [
          {
            "node": "Extract payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract payload": {
      "main": [
        [
          {
            "node": "Taskade: list concept tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Taskade: list concept tasks": {
      "main": [
        [
          {
            "node": "Parse channels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse channels": {
      "main": [
        [
          {
            "node": "Taskade: create Work Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Taskade: create Work Item": {
      "main": [
        [
          {
            "node": "Langfuse: trace fan-out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Langfuse: trace fan-out": {
      "main": [
        [
          {
            "node": "Respond to Taskade",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveExecutionProgress": true,
    "saveDataSuccessExecution": "all",
    "saveDataErrorExecution": "all",
    "timezone": "Europe/Berlin"
  },
  "tags": [
    {
      "name": "taskade"
    },
    {
      "name": "ruflo"
    }
  ]
}