{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "AI Writes the Post, Canvora Designs It (Weekly Content on Autopilot)",
  "nodes": [
    {
      "id": "st000000-0000-4000-8000-000000000000",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        -160
      ],
      "parameters": {
        "width": 560,
        "height": 992,
        "content": "## AI Writes the Post, Canvora Designs It (Weekly Content on Autopilot)\n\n### How it works\n\nThis workflow runs every Monday morning to generate a weekly social post concept with AI, then sends it to Canvora to create branded designs. It polls Canvora until the generation completes, handles timeout or failure cases, and posts either a review-ready Slack message or a failure alert.\n\n### Setup steps\n\n- Configure the schedule trigger with the desired timezone and weekly run time.\n- Add OpenAI credentials in the OpenAI Write Post Concept node (or swap in Anthropic/Gemini \u2014 any text model works).\n- Create a free Canvora account at [canvora.ai](https://canvora.ai) \u2014 free credits included, no card required \u2014 and get an API key from canvora.ai \u2192 Integrations \u2192 API Keys (available on all plans).\n- In both Canvora HTTP Request nodes, use one shared Header Auth credential: name `X-API-Key`, value your `vd_...` key.\n- Set the campaign values in Set Campaign Parameters: topic, comma-separated formats, optional language, optional brand kit ID.\n- Connect Slack credentials and set the target channel for review messages and failure alerts.\n\n### Customization\n\n- Formats: 100+ available (carousels, quote_card, stat_card, blog_header, \u2026) \u2014 list them with GET https://api.canvora.ai/api/formats. Cost: 10 credits per visual, 15 per carousel slide.\n- Language: set e.g. `es` to run the whole pipeline in Spanish \u2014 Canvora generates natively in 150+ languages.\n- Brand kits: pass a brandId (GET /api/brands) so every visual uses your colors, fonts, and logo.\n- Adjust the polling interval, timeout logic, and Slack wording to fit your cadence."
      },
      "typeVersion": 1
    },
    {
      "id": "st010000-0000-4000-8000-000000000000",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Schedule campaign inputs\n\nStarts the workflow on a weekly schedule and defines the campaign parameters that will guide the content and design generation."
      },
      "typeVersion": 1
    },
    {
      "id": "st020000-0000-4000-8000-000000000000",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Generate post concept\n\nUses OpenAI to write the post concept, then extracts and structures the generated concept for downstream use."
      },
      "typeVersion": 1
    },
    {
      "id": "st030000-0000-4000-8000-000000000000",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        384,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 400,
        "content": "## Start Canvora generation\n\nSubmits the prepared concept and campaign settings to the Canvora generation API to begin creating the visual assets."
      },
      "typeVersion": 1
    },
    {
      "id": "st040000-0000-4000-8000-000000000000",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 544,
        "content": "## Poll generation status\n\nWaits between status checks, fetches the latest Canvora generation state, determines whether generation is complete, and loops until completion or timeout."
      },
      "typeVersion": 1
    },
    {
      "id": "st050000-0000-4000-8000-000000000000",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 320,
        "content": "## Prepare success review\n\nChecks whether the completed generation succeeded, sorts the output assets into the intended order, and sends a Slack review message for the finished content."
      },
      "typeVersion": 1
    },
    {
      "id": "st060000-0000-4000-8000-000000000000",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 336,
        "content": "## Send failure alert\n\nPosts a Slack notification when the Canvora generation fails or exceeds the timeout threshold."
      },
      "typeVersion": 1
    },
    {
      "id": "n00200000-0000-4000-8000-00000000000",
      "name": "When Monday at 9am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -480,
        80
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "n00300000-0000-4000-8000-00000000000",
      "name": "Set Campaign Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        -260,
        80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "topic",
              "type": "string",
              "value": "practical productivity systems for solo founders"
            },
            {
              "id": "s2",
              "name": "formats",
              "type": "string",
              "value": "instagram_post,linkedin_post"
            },
            {
              "id": "s3",
              "name": "language",
              "type": "string",
              "value": ""
            },
            {
              "id": "s4",
              "name": "brandId",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "n00400000-0000-4000-8000-00000000000",
      "name": "OpenAI Write Post Concept",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -40,
        80
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a social content strategist. Write ONE specific, non-generic content concept (2-3 sentences) for a social media post about: {{ $('Set Campaign Parameters').item.json.topic }}. Use a concrete angle (a mistake, a number, a contrarian take, a how-to). Return ONLY the concept text, no preamble."
            }
          ]
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "n00500000-0000-4000-8000-00000000000",
      "name": "Extract Post Concept Code",
      "type": "n8n-nodes-base.code",
      "position": [
        180,
        80
      ],
      "parameters": {
        "jsCode": "const r = $input.first().json;\nconst idea = (r.message && r.message.content) || r.output || r.text || '';\nif (!String(idea).trim()) throw new Error('The AI step returned no concept text');\nreturn [{ json: { idea: String(idea).trim() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "n00600000-0000-4000-8000-00000000000",
      "name": "Post Concept to Canvora API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        432,
        80
      ],
      "parameters": {
        "url": "https://api.canvora.ai/api/generations",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify(Object.assign({\n  inputType: 'idea',\n  inputContent: $('Extract Post Concept Code').item.json.idea,\n  title: 'AI autopilot: ' + $('Extract Post Concept Code').item.json.idea.slice(0, 60),\n  outputFormats: $('Set Campaign Parameters').item.json.formats.split(',').map(s => s.trim()).filter(Boolean)\n},\n$('Set Campaign Parameters').item.json.language ? { language: $('Set Campaign Parameters').item.json.language.trim() } : {},\n$('Set Campaign Parameters').item.json.brandId ? { brandId: $('Set Campaign Parameters').item.json.brandId.trim() } : {}\n)) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "n00700000-0000-4000-8000-00000000000",
      "name": "Wait 30 Seconds for Canvora",
      "type": "n8n-nodes-base.wait",
      "position": [
        704,
        80
      ],
      "parameters": {
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "n00800000-0000-4000-8000-00000000000",
      "name": "Fetch Canvora Generation Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        928,
        80
      ],
      "parameters": {
        "url": "=https://api.canvora.ai/api/generations/{{ $('Post Concept to Canvora API').item.json.generation.id }}",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "n00900000-0000-4000-8000-00000000000",
      "name": "If Generation Completed",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e-done",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ ['completed', 'failed', 'partial', 'cancelled'].includes($json.generation.status) }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "n01000000-0000-4000-8000-00000000000",
      "name": "If Generation Timeout",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e-timeout",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $runIndex >= 20 }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "n01100000-0000-4000-8000-00000000000",
      "name": "If Generation Successful",
      "type": "n8n-nodes-base.if",
      "position": [
        1376,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e-success",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ ['completed', 'partial'].includes($json.generation.status) }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "n01200000-0000-4000-8000-00000000000",
      "name": "Send Slack Failure Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        1540,
        380
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Canvora generation didn't finish for this week's AI concept (status: {{ $json.generation ? $json.generation.status : 'timeout' }}). Failed generations refund credits automatically. Check https://canvora.ai/dashboard",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#content"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "n01300000-0000-4000-8000-00000000000",
      "name": "Sort Outputs by Order",
      "type": "n8n-nodes-base.code",
      "position": [
        1616,
        0
      ],
      "parameters": {
        "jsCode": "// Sort outputs by sortOrder (carousel slides can complete out of order)\nconst g = $input.first().json.generation;\nconst outputs = (g.outputs || [])\n  .slice()\n  .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));\n\nconst lines = outputs.map((o, i) => `${i + 1}. ${o.format || 'visual'} \u2014 ${o.fileUrl}`);\n\nconst text = [\n  `\ud83c\udfa8 *New visuals ready* for \"${g.title || 'your content'}\"`,\n  `${outputs.length} image(s)${g.status === 'partial' ? ' (some formats failed and were refunded)' : ''}:`,\n  '',\n  ...lines,\n  '',\n  `Review or edit: https://canvora.ai/generations/${g.id}`\n].join('\\n');\n\nreturn [{ json: { text, outputs, generationId: g.id } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "n01400000-0000-4000-8000-00000000000",
      "name": "Post Review to Slack Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        1824,
        0
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#content"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "When Monday at 9am": {
      "main": [
        [
          {
            "node": "Set Campaign Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Generation Timeout": {
      "main": [
        [
          {
            "node": "Send Slack Failure Notification",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 30 Seconds for Canvora",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort Outputs by Order": {
      "main": [
        [
          {
            "node": "Post Review to Slack Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Generation Completed": {
      "main": [
        [
          {
            "node": "If Generation Successful",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If Generation Timeout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Campaign Parameters": {
      "main": [
        [
          {
            "node": "OpenAI Write Post Concept",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Generation Successful": {
      "main": [
        [
          {
            "node": "Sort Outputs by Order",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Slack Failure Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Post Concept Code": {
      "main": [
        [
          {
            "node": "Post Concept to Canvora API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Write Post Concept": {
      "main": [
        [
          {
            "node": "Extract Post Concept Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Concept to Canvora API": {
      "main": [
        [
          {
            "node": "Wait 30 Seconds for Canvora",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 30 Seconds for Canvora": {
      "main": [
        [
          {
            "node": "Fetch Canvora Generation Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Canvora Generation Status": {
      "main": [
        [
          {
            "node": "If Generation Completed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}