Automatisation de blog Shopify : articles optimisés SEO/AEO basés sur une liste de mots-clés

Avancé

Ceci est unContent Creation, Multimodal AIworkflow d'automatisation du domainecontenant 32 nœuds.Utilise principalement des nœuds comme If, Set, Code, Merge, HttpRequest. Utiliser GPT-4 et Google Sheets pour générer des articles de blog Shopify optimisés SEO/AEO

Prérequis
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
  • Informations d'identification Google Sheets API
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "id": "fpun8HUSy6VdVm9l",
  "meta": {
    "instanceId": "8e9162e70be518ca153a70a16d8785f5bfc6523821e135712fb7ef93fe97a5dd",
    "templateCredsSetupCompleted": true
  },
  "name": "Shopify Blog on autopilot: SEO/AEO-optimized articles from a keyword List",
  "tags": [],
  "nodes": [
    {
      "id": "f5411ac3-3df5-4d11-8458-81fd89b11874",
      "name": "Déclencheur Manuel",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -384,
        48
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "62f498a7-861e-4264-ad79-30be7945957d",
      "name": "Déclencheur Planifié",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -384,
        -144
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 2,5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4c86423b-5357-4e8d-84cb-96e8491ace80",
      "name": "Shopify: Créer un Article (REST)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        992,
        592
      ],
      "parameters": {
        "url": "=https://{{$items('Set - Config')[0].json.shopDomain}}/admin/api/{{$items('Set - Config')[0].json.shopApiVersion}}/blogs/{{$items('Set - Config')[0].json.blogId}}/articles.json",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({\n  article: {\n    title: $('Code - Sanitize + pick non-conflicting slug').item.json.title,\n    handle: $('Code - Sanitize + pick non-conflicting slug').item.json.slug,\n    author: $items('Set - Config')[0].json.author || 'Equipo',\n    tags: ($('Code - Sanitize + pick non-conflicting slug').item.json.tags || []).join(', '),\n    summary_html: $('Code - Sanitize + pick non-conflicting slug').item.json.summary,\n    body_html: $('Code - Sanitize + pick non-conflicting slug').item.json.content_html,\n    published: $items('Set - Config')[0].json.autoPublish === 'true' || $items('Set - Config')[0].json.autoPublish === true,\n    image: $json.data[0].b64_json\n      ? {\n          attachment: $json.data[0].b64_json,\n          alt: $('Code - Sanitize + pick non-conflicting slug').item.json.image_alt\n        }\n      : null\n  }\n}) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "2sKJ6R3gCnrct0e3",
          "name": "Shopify Admin Token"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ff02026b-1083-47de-83cc-4809ecce14fb",
      "name": "Construire l'ID d'Article",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        592
      ],
      "parameters": {
        "jsCode": "const id = $input.first().json.article.id;\nif (!id) throw new Error('No article.id returned');\nreturn [{ json: { ...$input, articleId: id, articleGid: `gid://shopify/Article/${id}` } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "eae2f1a4-9a71-422e-846d-ef9db7c7885b",
      "name": "Shopify: metafieldsSet (GraphQL)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1392,
        592
      ],
      "parameters": {
        "url": "=https://{{$items('Set - Config')[0].json.shopDomain}}/admin/api/{{$items('Set - Config')[0].json.shopApiVersion}}/graphql.json",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $metafields) { metafields { id key value } userErrors { field message } } }\",\n  \"variables\": {\n    \"metafields\": [\n      {\n        \"ownerId\": \"{{$json.articleGid}}\",\n        \"namespace\": \"global\",\n        \"key\": \"title_tag\",\n        \"type\": \"single_line_text_field\",\n        \"value\": \"{{ $('Code - Sanitize + pick non-conflicting slug').item.json.seo_title }}\"\n      },\n      {\n        \"ownerId\": \"{{$json.articleGid}}\",\n        \"namespace\": \"global\",\n        \"key\": \"description_tag\",\n        \"type\": \"single_line_text_field\",\n        \"value\": \"{{ $('Code - Sanitize + pick non-conflicting slug').item.json.seo_description }}\"\n      }\n    ]\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "2sKJ6R3gCnrct0e3",
          "name": "Shopify Admin Token"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7db3c0c6-06c7-4812-9b4e-b7f37218e0a3",
      "name": "Note Adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -688
      ],
      "parameters": {
        "width": 720,
        "height": 880,
        "content": "## Google sheets structure\n* Create a Google Sheet with 3 tabs: Keywords, Links, Published\n* Keywords tab:\n** A: Keyword\n** B: Cluster (pillar/topic group)\n** C: Intent (informational / transactional / navigational)\n** D: volume_sum (sum of volume for this keyword from Semrush or other similar websites)\n** E: difficulty_avg (difficulty for this keyword from Semrush or other similar websites)\n** F: Priority (1–5)\n* Links tab (for internal linking within the articles): \n** A: URL (absolute URL)\n** C: Keywords (keywords that can be linked to the url)\n* Published tab (to keep track of published article): \n** A Datetime\n** B Keyword\n** C Cluster\n** D Title\n** E Slug\n** F URL\n** G Status (false if unpublished/true if published)"
      },
      "typeVersion": 1
    },
    {
      "id": "4c0bdb78-5bdd-429a-aed8-5b1de72f8b6a",
      "name": "Note Adhésive1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        -688
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 880,
        "content": "## CONFIG\n**Important** to edit the config with your information.\nVariables that **YOU MUST** edit:\n* shopDomain=your-store.myshopify.com\n* siteBaseUrl=https://your-domain (if you have one) or https://your-store.myshopify.com\n* blogHandle= the blog handle is just the slug after /blogs/\n* author: which author you want to use to publish the article.\n* sheetId=<YOUR_SHEET_ID>\n\nVariables that you can leave as they are:\n* shopApiVersion=2025-07\n* tz=Europe/Madrid\n* lang=en-EN\n* maxPerRun=1 (how many articles to write per run)\n* autoPublish=false (creating a draft -> change to true if you want to publish directly)"
      },
      "typeVersion": 1
    },
    {
      "id": "6da08969-64a5-487d-969b-ed6567b08687",
      "name": "Sheets - Lire les Mots-clés",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        -240
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Keywords"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.sheetId }}"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "IakWbbx0a4c1W8qa",
          "name": "Google Service Account account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "ddf9b47b-16fa-40b6-8b64-f675bef27de8",
      "name": "Sheets - Lire les Liens",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        48
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Links"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set - Config').item.json.sheetId }}"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "IakWbbx0a4c1W8qa",
          "name": "Google Service Account account"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.7
    },
    {
      "id": "4885986d-2d2f-4335-9ff2-1ae727b64206",
      "name": "Sheets - Lire les Articles Publiés",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        -96
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Published"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set - Config').item.json.sheetId }}"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "IakWbbx0a4c1W8qa",
          "name": "Google Service Account account"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.7
    },
    {
      "id": "a5706020-9dc0-4b8c-804f-85167d6dff33",
      "name": "Code - Normaliser les Entrées",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        -96
      ],
      "parameters": {
        "jsCode": "/**\n * Buckets:\n *  - Keywords  (keywords/keyword, cluster, intent, priority, volume_sum?, difficulty_avg?)\n *  - Published (Datetime, Keyword, Cluster, Title, Slug/Handle, URL, Status)\n *\n * Output:\n * {\n *   keywords: [{ keyword, cluster, intent, priority, volume_sum?, difficulty_avg?, slug }],\n *   published: {\n *     slugs: string[],\n *     clusters: string[],\n *     slugToUrl: { [slug]: url },\n *     pairs: string[]   // \"keyword||cluster\" normalized\n *   }\n * }\n */\n\nconst toAscii = (s='') =>\n  String(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/ñ/gi,'n');\n\nconst toSlug = (s='') => toAscii(String(s).toLowerCase())\n  .replace(/[^a-z0-9\\s-]/g,'')\n  .trim().replace(/\\s+/g,'-').replace(/-+/g,'-')\n  .slice(0,65).replace(/-+$/,'');\n\nconst isHttp = (s='') => /^https?:\\/\\//i.test(String(s||'').trim());\n\nconst lcKeys = (obj={}) => {\n  const out = {};\n  for (const [k,v] of Object.entries(obj)) out[String(k).toLowerCase()] = v;\n  return out;\n};\n\n// normalized pair helpers\nconst norm = (s='') =>\n  toAscii(String(s).toLowerCase().trim().replace(/\\s+/g,' '));\nconst pairKey = (k, c) =>\n  `${norm(k)}||${norm(c || '(unclustered)')}`;\n\n// Collect all merged rows\nconst rows = $input.all().map(i => i.json).filter(Boolean);\n\n// Buckets\nconst kwRows = [];\nconst pubRows = [];\n\n// Classify rows; explicitly ignore Links rows\nfor (const r0 of rows) {\n  const r = lcKeys(r0);\n  if (!Object.values(r).some(v => String(v||'').trim())) continue;\n\n  // IGNORE Links rows (Key + URL [+ Anchor])\n  if (r.key && r.url && isHttp(r.url)) { continue; }\n\n  // Published: has a slug (or handle) and either URL or Title or Keyword\n  if ((r.slug || r.handle) && (r.url || r.title || r.keyword || r.keywords)) { pubRows.push(r); continue; }\n\n  // Keywords: has 'keywords' or 'keyword' column\n  if (r.keywords || r.keyword) { kwRows.push(r); continue; }\n\n  // Fallbacks (conservative): ignore rows that only have URL\n  if (isHttp(r.url)) { continue; }\n}\n\n// --- Normalize Keywords ---\nconst seen = new Set(); // de-dupe identical keyword->slug rows\nconst keywords = [];\nfor (const r of kwRows) {\n  const keyword = String(r.keywords ?? r.keyword ?? '').trim();\n  if (!keyword) continue;\n\n  const clusterRaw = String(r.cluster ?? '').trim();\n  const cluster    = clusterRaw || '(unclustered)';\n  const intent     = String(r.intent ?? 'informational').trim();\n\n  const pRaw       = String(r.priority ?? r.prioridad ?? '3').trim();\n  const pNum       = parseInt(pRaw, 10);\n  const priority   = Math.min(5, Math.max(1, Number.isFinite(pNum) ? pNum : 3));\n\n  const volNum     = Number(r.volume_sum ?? r.volume);\n  const kdNum      = Number(r.difficulty_avg ?? r.difficulty ?? r.kd);\n  const volume_sum     = Number.isFinite(volNum) ? volNum : undefined;\n  const difficulty_avg = Number.isFinite(kdNum) ? kdNum : undefined;\n\n  const slug = toSlug(keyword);\n  if (!slug || seen.has(slug)) continue;\n  seen.add(slug);\n\n  keywords.push({ keyword, cluster, intent, priority, volume_sum, difficulty_avg, slug });\n}\n\n// --- Normalize Published ---\nconst slugs = new Set();\nconst clusters = new Set();\nconst slugToUrl = {};\nconst pairs = new Set();\n\nfor (const r of pubRows) {\n  const slug = String((r.slug ?? r.handle ?? '')).trim().toLowerCase();\n  if (slug) {\n    slugs.add(slug);\n    if (isHttp(r.url)) slugToUrl[slug] = String(r.url).trim();\n  }\n\n  const clCell = String(r.cluster ?? '').trim();\n  if (clCell) clusters.add(toSlug(clCell));\n\n  // Build (keyword, cluster) pair if the Published sheet includes them\n  const kwCell = String((r.keyword ?? r.keywords ?? '')).trim();\n  if (kwCell) {\n    pairs.add(pairKey(kwCell, clCell || '(unclustered)'));\n  }\n}\n\n// Emit consolidated item (no Links)\nreturn [{\n  json: {\n    keywords,\n    published: {\n      slugs: Array.from(slugs),\n      clusters: Array.from(clusters),\n      slugToUrl,\n      pairs: Array.from(pairs)\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3c1b2333-12c8-4c9f-a154-f13897e33f61",
      "name": "Fusionner",
      "type": "n8n-nodes-base.merge",
      "position": [
        544,
        -96
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "d62e4586-c8de-4dbc-80c7-96c37ab035d6",
      "name": "Code - Choisir un Candidat",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        -96
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst cfg  = ($items('Set - Config')[0] || { json: {} }).json;\n\n// settings\nconst maxPerRun = Math.max(1, parseInt(cfg.maxPerRun ?? '1', 10));\nconst onePerCluster = true;\nconst avoidPublishedClusters = false;\n\n// helpers\nconst num = (v,d=0)=>{ const n=Number(v); return Number.isFinite(n)?n:d; };\nconst toAscii = s=>String(s||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/ñ/gi,'n');\nconst norm = s=>toAscii(String(s).toLowerCase().trim().replace(/\\s+/g,' '));\nconst pairKey = (k,c)=>`${norm(k)}||${norm(c||'(unclustered)')}`;\nconst lc = s=>String(s||'').toLowerCase();\n\n// lookups (from Normalize)\nconst publishedPairs = new Set((data.published?.pairs || []).map(String));\nconst publishedClusters = new Set((data.published?.clusters || []).map(lc));\n\n// eligible (dedupe by keyword+cluster)\nconst seenPairs = new Set();\nlet candidates = [];\nfor (const k of (data.keywords || [])) {\n  if (!k || !k.keyword) continue;\n  const pKey = pairKey(k.keyword, k.cluster);\n  if (publishedPairs.has(pKey)) continue;\n  if (seenPairs.has(pKey)) continue;\n  seenPairs.add(pKey);\n  candidates.push(k);\n}\n\n// optional: avoid clusters that already have content\nif (avoidPublishedClusters) {\n  candidates = candidates.filter(k => !publishedClusters.has(lc(k.cluster)));\n}\n\n// sort: priority (5 best) → volume desc → difficulty asc → keyword A→Z\ncandidates.sort((a,b)=>{\n  const pa=num(a.priority,3), pb=num(b.priority,3);\n  if (pa!==pb) return pb-pa;\n  const va=num(a.volume_sum??a.volume,0), vb=num(b.volume_sum??b.volume,0);\n  if (va!==vb) return vb-va;\n  const da=num(a.difficulty_avg??a.difficulty??a.kd,999), db=num(b.difficulty_avg??b.difficulty??b.kd,999);\n  if (da!==db) return da-db;\n  return String(a.keyword||'').localeCompare(String(b.keyword||''));\n});\n\n// pick up to maxPerRun, one-per-cluster if enabled\nconst picks=[], seenClusters=new Set();\nfor (const k of candidates) {\n  if (picks.length>=maxPerRun) break;\n  const ck = lc(k.cluster||'(unclustered)');\n  if (onePerCluster && seenClusters.has(ck)) continue;\n  picks.push(k); seenClusters.add(ck);\n}\n\nif (!picks.length) return [{ json:{ skip:true, reason:'No eligible keywords after (keyword,cluster) dedupe.' }}];\nreturn picks.map(p=>({ json:p }));"
      },
      "typeVersion": 2
    },
    {
      "id": "4144bece-9034-4d44-986a-d79c1acc9559",
      "name": "Shopify - Lister les Slugs d'Articles",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1664,
        -96
      ],
      "parameters": {
        "url": "=https://{{$items('Set - Config')[0].json.shopDomain}}/admin/api/{{$items('Set - Config')[0].json.shopApiVersion}}/graphql.json",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={{ JSON.stringify({\n  query: \"query getArticles($id: ID!, $after: String){ node(id:$id){ ... on Blog { id handle articles(first:250, after:$after){ edges{ node{ id handle } } pageInfo{ hasNextPage endCursor } } } } }\",\n  variables: Object.assign(\n    { id: `gid://shopify/Blog/${$items('Set - Config')[0].json.blogId}` },\n    $json.__cursor ? { after: $json.__cursor } : {}\n  )\n}) }}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpBearerAuth": {
          "id": "lA5pPKE4T8YORtpH",
          "name": "Bearer Auth account"
        },
        "httpHeaderAuth": {
          "id": "2sKJ6R3gCnrct0e3",
          "name": "Shopify Admin Token"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ad70b29e-ae77-4bb8-ab6b-0ce1bf23d18c",
      "name": "Set - Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -48,
        -64
      ],
      "parameters": {
        "values": {
          "number": [
            {
              "name": "maxPerRun",
              "value": 1
            }
          ],
          "string": [
            {
              "name": "shopDomain"
            },
            {
              "name": "siteBaseUrl"
            },
            {
              "name": "blogId"
            },
            {
              "name": "blogHandle"
            },
            {
              "name": "tz",
              "value": "Europe/Madrid"
            },
            {
              "name": "lang",
              "value": "en-EN"
            },
            {
              "name": "shopApiVersion",
              "value": "2025-07"
            },
            {
              "name": "autoPublish",
              "value": "false"
            },
            {
              "name": "sheetId"
            },
            {
              "name": "author"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 2
    },
    {
      "id": "4d99e350-ac86-4128-96d1-3e827a6a4b92",
      "name": "If - Plus de pages ?",
      "type": "n8n-nodes-base.if",
      "position": [
        2112,
        -96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "24406e83-c21f-4a0a-948b-c80e3581d54e",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.__hasNext === true }}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7dc7d93d-af2b-4c77-884a-7f4cb70f79cf",
      "name": "Note Adhésive2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        -288
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 480,
        "content": "## Pick a candidate\nPick a keyword/cluster based on priority, volume and difficulty. It takes keywords with highest priority first."
      },
      "typeVersion": 1
    },
    {
      "id": "15ab76cc-350d-4b6b-930b-c061b16ccb3a",
      "name": "Code - Initialiser l'Analyseur de Slug",
      "type": "n8n-nodes-base.code",
      "position": [
        1440,
        -96
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    existingSlugs: [],\n    __cursor: null\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bf9c87ee-9494-445a-9645-407de7a2b2e0",
      "name": "Code - Accumuler les Slugs + Curseur",
      "type": "n8n-nodes-base.code",
      "position": [
        1904,
        -96
      ],
      "parameters": {
        "jsCode": "// 1) Get prior state from the SAME item (if present)\nconst list = Array.isArray($json.existingSlugs) ? $json.existingSlugs.slice() : [];\nconst seen = new Set(list.map(s => String(s).toLowerCase()));\n\n// 2) Read Shopify response from body.*\nconst edges    = $json.body?.data?.node?.articles?.edges || [];\nconst pageInfo = $json.body?.data?.node?.articles?.pageInfo || {};\n\n// 3) Accumulate unique handles\nfor (const e of edges) {\n  const h = String(e?.node?.handle || '').trim().toLowerCase();\n  if (h && !seen.has(h)) {\n    seen.add(h);\n    list.push(h);\n  }\n}\n\n// 4) Emit updated state for the loop\nreturn [{\n  json: {\n    existingSlugs: list,\n    __cursor: pageInfo?.hasNextPage ? pageInfo?.endCursor : null,\n    __hasNext: !!pageInfo?.hasNextPage,\n    countAccumulated: list.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c1d53448-4627-4954-9881-e623f9c41d3b",
      "name": "Note Adhésive3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        -288
      ],
      "parameters": {
        "width": 960,
        "height": 480,
        "content": "## Pipeline to make a list of already used slugs from the shopify blog\nThis is a process to make a list of already used slugs so that in the next step, it doesn't create a slug already in use. "
      },
      "typeVersion": 1
    },
    {
      "id": "945286fe-135f-46ac-aaca-bebc8429fe14",
      "name": "Code - Construire l'Invite",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        592
      ],
      "parameters": {
        "jsCode": "const all = $input.all();\n\n// 1) Existing slugs\nconst existingSlugs = (all.find(i => i.json.existingSlugs) || { json: { existingSlugs: [] } }).json.existingSlugs;\n\n// 2) Keyword data\nconst kwItem = all.find(i => i.json.keyword) || { json: {} };\nconst kw = kwItem.json.keyword || '';\nconst cluster = kwItem.json.cluster || '';\nconst intent = kwItem.json.intent || '';\n\n// 3) Candidate internal links\nconst links = (all.find(i => i.json.links) || { json: { links: [] } }).json.links || [];\n\n// --- PROMPT (system + user) ---\nconst system = `You are a team composed of a senior editor (English, en-EN), and an SEO/AEO expert.\nYou write for humans (E-E-A-T): clear and useful, without fluff or exaggeration.\nDo not use first person or testimonials. Use a neutral, respectful, and didactic tone.\nUse sentence case in ALL content: titles (title, seo_title, H1, H2, H3), paragraphs, and image alt text, with capitalization only at the beginning of the sentence and for proper nouns or acronyms (e.g., SEO, AEO, HTML).\nDo not use Title Case or ALL CAPS for common words.\nDo not use semicolons (;) or double dashes (--). Replace them with a period or comma as appropriate.`;\n\n// --- NEW BLOCK: internal link policy ---\nconst internalLinkPolicy = `\n### Internal linking (evaluation and use)\n- I give you a list of internal links with their associated keywords:\n${JSON.stringify(links, null, 2)}\n- Goal: **insert exactly 1 (one) internal link** in the article **only if** it is relevant.\n- Minimum relevance: there is a clear semantic match between the target keyword (\"${kw}\"), the cluster (\"${cluster}\"), the article content or its sections, and the keywords associated with the link.\n- Selection preferences:\n  - Direct keyword match > partial match > thematic relation.\n- Insertion:\n  - Place the link **inside an existing paragraph**, naturally (not at the beginning or end of the article).\n  - Use a **descriptive and natural anchor** (not \"click here\").\n  - Insert **only that link** with the tag <a href=\"...\">…</a>. Do not add more links or invent URLs.\n  - Do not repeat the same URL more than once.\n- If **no link is clearly relevant**, **do not insert any**.\n`;\n\n// --- Your original user prompt + additions ---\nconst user = `\nWrite **ONE** SEO- and AEO-optimized article about the keyword: \"${kw}\".\nCluster: ${cluster}. Search intent: ${intent}. Language: English (en-EN).\nAvoid repeating slugs already used in the blog: [${existingSlugs.join(', ')}].\n\n${internalLinkPolicy}\n\nReturn ONLY JSON in this exact shape:\n{\n  \"title\": string,\n  \"slug\": string,                   // kebab-case ascii, ≤65, NOT included in [${existingSlugs.slice(0,50).join(', ')}]\n  \"backup_slugs\": string[],         // 2–4 valid and free alternatives\n  \"seo_title\": string,              // ≤70\n  \"seo_description\": string,        // ≤160\n  \"tags\": string[],                 // 3–8\n  \"content_html\": string,           // ONLY: h2,h3,p,ul,ol,li,a,strong,em,blockquote,code,pre\n  \"resumen\": string,                // will appear on the blog home\n  \"image_prompt\": string,           // brief for hero image, without overlay text\n  \"image_alt\": string,              // ≤120 chars\n  \"internal_link_used\": {           // NEW — meta for control\n    \"url\": string | null,           // URL used or null if no relevant link\n    \"anchor\": string | null,        // anchor text used or null\n    \"reason\": string | null         // brief justification of relevance or null\n  }\n}\n\n### Hard rules (comply with all):\n- Start with a **Direct Answer** block (35–60 words) that responds to the user’s intent without digressions.\n- **Structure**: H2/H3 by intent (informational: definition → usefulness → how to apply it → common mistakes → FAQs; comparative: criteria → alternatives → pros/cons → FAQs).\n- **SEO/AEO**: the keyword appears in the first 100 words and in an H2 (without over-optimizing); use synonyms and related entities naturally.\n- **Length**: ≥900 words unless the keyword is clearly short.\n- **Lists** scannable; short paragraphs (≤4 lines).\n- content_html must not contain <h1>\n- **Internal links**:\n  - Keep your 3–6 **suggested internal anchors** (anchor text without URL, as already indicated).\n  - **Additionally** apply the internal link policy above to insert **exactly 1** <a href=\"real URL\"> inside a paragraph, **only if** there is clear relevance. If not, insert none.\n- **Language**: avoid absolutes and unnecessary jargon; briefly define technical terms.\n- Sentence case in titles and text.\n- Do not use ';' or '--'.\n\n### Validations before returning JSON (do not show them):\n- Wordcount ≥900 approx. in body.\n- Character limits ok.\n- Valid, unique slug not similar to those given.\n- Keyword present in the first 100 words and an H2.\n- 3–6 suggested internal anchors inside paragraphs (without URL).\n- **If 'internal_link_used.url' is not null**:\n  - 'content_html' contains **exactly one** occurrence of that URL as <a href=\"...\">…</a>.\n  - There is no other different URL.\n- **If 'internal_link_used.url' is null**:\n  - 'content_html' does not contain any <a href=\"http\".\n- Sentence case and no ';' or '--'.\n`;\n\nreturn [{\n  json: {\n    messages: [\n      { role: 'system', content: system },\n      { role: 'user', content: user }\n    ],\n    response_format: { type: 'json_object' }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "980b1d19-816f-4556-a082-d734a65b0868",
      "name": "OpenAI - Chat Completions",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        592
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({\n  model: \"gpt-4o-mini\",\n  temperature: 0.5,\n  response_format: $json.response_format,\n  messages: $json.messages\n}) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "V3HO9ZNvIwrMeJ2f",
          "name": "OpenAI n8n workflow"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7048dcfd-b13e-420e-805b-571213d55c66",
      "name": "Code - Nettoyer + choisir un slug non conflictuel",
      "type": "n8n-nodes-base.code",
      "position": [
        464,
        592
      ],
      "parameters": {
        "jsCode": "const existing = new Set($('Merge - Wiring').first().json.existingSlugs.map(s => String(s).toLowerCase()));\nconst resp = $input.first().json;\nlet ai;\ntry { ai = JSON.parse(resp.choices?.[0]?.message?.content || \"{}\"); } catch { throw new Error(\"LLM JSON inválido\"); }\n\nconst keep = /(\\/?)(h1|h2|h3|p|ul|ol|li|a|strong|em|blockquote|code|pre)(\\s+[^>]*)?>/i;\nconst clean = (html=\"\") => html.replace(/<[^>]+>/g, t => keep.test(t) ? t : \"\");\n\nconst toAscii = s => s.normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/ñ/gi,'n');\nconst toSlug = s => toAscii(String(s||'').toLowerCase())\n  .replace(/[^a-z0-9\\s-]/g,'').trim().replace(/\\s+/g,'-').replace(/-+/g,'-').slice(0,65).replace(/-+$/,'');\nconst cut = (s,n)=>{s=String(s||'');return s.length<=n?s:s.slice(0,n-1).replace(/\\s+\\S*$/,'')+'…';};\n\nconst preferSlug = (s, backups=[]) => {\n  let cand = toSlug(s);\n  if (!existing.has(cand)) return cand;\n  for (const b of backups) {\n    const bs = toSlug(b);\n    if (bs && !existing.has(bs)) return bs;\n  }\n  // suffix fallback\n  let i = 2;\n  while (i < 10) {\n    const tryS = (cand + '-' + i).slice(0,65);\n    if (!existing.has(tryS)) return tryS;\n    i++;\n  }\n  return cand; // last resort\n};\n\nconst title = cut(ai.title || \"\", 90);\nconst slug = preferSlug(ai.slug || ai.title, Array.isArray(ai.backup_slugs) ? ai.backup_slugs : []);\nconst seo_title = cut(ai.seo_title || title, 70);\nconst seo_description = cut(ai.seo_description || \"\", 160);\nconst summary = ai.resumen;\nlet html = clean(ai.content_html || \"\");\n\n// Ensure single H1\nif (!/<h1>/i.test(html)) html = `<h1>${title}</h1>` + html;\n\n\nreturn [{\n  json: {\n    title, slug, seo_title, seo_description, summary, \n    tags: Array.isArray(ai.tags) ? ai.tags.slice(0,8) : [],\n    content_html: html,\n    image_prompt: String(ai.image_prompt || ''),\n    image_alt: cut(ai.image_alt || '', 120),\n    existingSlugs: Array.from(existing),\n    blogHandle: $('Set - Config').first().json.blogHandle\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a072d4af-4421-4c72-8a65-f85c9d195c33",
      "name": "HTTP Request - OpenAI Images (Hero)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        720,
        592
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/images/generations",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({\n  model: \"gpt-image-1\",\n  prompt: `${$json.image_prompt}`,\n  size: \"1536x1024\",\n  n: 1\n}) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "V3HO9ZNvIwrMeJ2f",
          "name": "OpenAI n8n workflow"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "932eb48e-a0e7-43cd-b45b-f5ab0e38889b",
      "name": "Merge - Câblage",
      "type": "n8n-nodes-base.merge",
      "position": [
        1728,
        208
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "726c102b-8cc4-48d0-9adc-b65a6d42911b",
      "name": "Note Adhésive4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        432
      ],
      "parameters": {
        "color": 4,
        "width": 944,
        "height": 432,
        "content": "## Prepare the article with OpenAI to get the content and the hero image"
      },
      "typeVersion": 1
    },
    {
      "id": "2fc47ec6-f572-47c2-a5db-eaaa6e176612",
      "name": "Note Adhésive5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        432
      ],
      "parameters": {
        "color": 6,
        "width": 592,
        "height": 432,
        "content": "## Create shopify article and update metafields for SEO"
      },
      "typeVersion": 1
    },
    {
      "id": "55d9c6f3-9577-420e-abd5-ca5bb6939897",
      "name": "Note Adhésive6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1584,
        416
      ],
      "parameters": {
        "color": 3,
        "width": 608,
        "height": 448,
        "content": "## Update to Google Sheets\n* Update the \"Published\" tab to keep track of keywords that were already used.\n* Update the \"Links\" tab, so that in the next articles, this link can be used for internal linking."
      },
      "typeVersion": 1
    },
    {
      "id": "dcc2314f-d7dd-4789-9318-6d70a2a67346",
      "name": "Ajouter une ligne à l'onglet \"Publiés\"",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1776,
        528
      ],
      "parameters": {
        "columns": {
          "value": {
            "URL": "=/{{ $('Code - Sanitize + pick non-conflicting slug').item.json.slug }}",
            "Slug": "={{ $('Code - Sanitize + pick non-conflicting slug').item.json.slug }}",
            "Title": "={{ $('Code - Sanitize + pick non-conflicting slug').item.json.title }}",
            "Status": "={{$items('Set - Config')[0].json.autoPublish}}",
            "Cluster": "={{ $('Code - Pick Candidate').first().json.cluster }}",
            "Keyword": "={{ $('Code - Pick Candidate').first().json.keyword }}",
            "Datetime": "={{ $('Build Article GID').item.json.context.response.body.article.created_at }}"
          },
          "schema": [
            {
              "id": "Datetime",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Datetime",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Cluster",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Cluster",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Slug",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Slug",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Published"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{$items('Set - Config')[0].json.sheetId}}"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "IakWbbx0a4c1W8qa",
          "name": "Google Service Account account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "fb03a884-40fb-4622-8bed-a110616af897",
      "name": "Code - Liste des liens pour l'article",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        208
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      links: items.map(item => ({\n        url: item.json.URL,\n        keywords: item.json.Keywords.split(\",\").map(k => k.trim())\n      }))\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "90f2c446-cad7-4249-b115-43db941fefb7",
      "name": "Ajouter une ligne à l'onglet \"Liens\"",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1776,
        704
      ],
      "parameters": {
        "columns": {
          "value": {
            "URL": "=/{{ $('Build Article GID').item.json.context.response.body.article.handle }}",
            "Keywords": "={{ $('Code - Pick Candidate').first().json.keyword }}"
          },
          "schema": [
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keywords",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keywords",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Links"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set - Config').first().json.sheetId }}"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "IakWbbx0a4c1W8qa",
          "name": "Google Service Account account"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5b24d0c3-6850-4020-9650-02525a971fce",
      "name": "Note Adhésive7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -448,
        -256
      ],
      "parameters": {
        "height": 272,
        "content": "There is a cron to run the process each Tuesday and Friday at 9AM."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "ib0LqomNsNIqEoi6",
    "executionOrder": "v1"
  },
  "versionId": "3928abaa-6112-4e63-a760-569ebcc3a448",
  "connections": {
    "3c1b2333-12c8-4c9f-a154-f13897e33f61": {
      "main": [
        [
          {
            "node": "a5706020-9dc0-4b8c-804f-85167d6dff33",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ad70b29e-ae77-4bb8-ab6b-0ce1bf23d18c": {
      "main": [
        [
          {
            "node": "6da08969-64a5-487d-969b-ed6567b08687",
            "type": "main",
            "index": 0
          },
          {
            "node": "ddf9b47b-16fa-40b6-8b64-f675bef27de8",
            "type": "main",
            "index": 0
          },
          {
            "node": "4885986d-2d2f-4335-9ff2-1ae727b64206",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f5411ac3-3df5-4d11-8458-81fd89b11874": {
      "main": [
        [
          {
            "node": "ad70b29e-ae77-4bb8-ab6b-0ce1bf23d18c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "932eb48e-a0e7-43cd-b45b-f5ab0e38889b": {
      "main": [
        [
          {
            "node": "945286fe-135f-46ac-aaca-bebc8429fe14",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4d99e350-ac86-4128-96d1-3e827a6a4b92": {
      "main": [
        [
          {
            "node": "4144bece-9034-4d44-986a-d79c1acc9559",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "932eb48e-a0e7-43cd-b45b-f5ab0e38889b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "62f498a7-861e-4264-ad79-30be7945957d": {
      "main": [
        [
          {
            "node": "ad70b29e-ae77-4bb8-ab6b-0ce1bf23d18c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ff02026b-1083-47de-83cc-4809ecce14fb": {
      "main": [
        [
          {
            "node": "eae2f1a4-9a71-422e-846d-ef9db7c7885b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "945286fe-135f-46ac-aaca-bebc8429fe14": {
      "main": [
        [
          {
            "node": "980b1d19-816f-4556-a082-d734a65b0868",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ddf9b47b-16fa-40b6-8b64-f675bef27de8": {
      "main": [
        [
          {
            "node": "fb03a884-40fb-4622-8bed-a110616af897",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d62e4586-c8de-4dbc-80c7-96c37ab035d6": {
      "main": [
        [
          {
            "node": "15ab76cc-350d-4b6b-930b-c061b16ccb3a",
            "type": "main",
            "index": 0
          },
          {
            "node": "932eb48e-a0e7-43cd-b45b-f5ab0e38889b",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "6da08969-64a5-487d-969b-ed6567b08687": {
      "main": [
        [
          {
            "node": "3c1b2333-12c8-4c9f-a154-f13897e33f61",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "15ab76cc-350d-4b6b-930b-c061b16ccb3a": {
      "main": [
        [
          {
            "node": "4144bece-9034-4d44-986a-d79c1acc9559",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a5706020-9dc0-4b8c-804f-85167d6dff33": {
      "main": [
        [
          {
            "node": "d62e4586-c8de-4dbc-80c7-96c37ab035d6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4885986d-2d2f-4335-9ff2-1ae727b64206": {
      "main": [
        [
          {
            "node": "3c1b2333-12c8-4c9f-a154-f13897e33f61",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "90f2c446-cad7-4249-b115-43db941fefb7": {
      "main": [
        []
      ]
    },
    "980b1d19-816f-4556-a082-d734a65b0868": {
      "main": [
        [
          {
            "node": "7048dcfd-b13e-420e-805b-571213d55c66",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4144bece-9034-4d44-986a-d79c1acc9559": {
      "main": [
        [
          {
            "node": "bf9c87ee-9494-445a-9645-407de7a2b2e0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4c86423b-5357-4e8d-84cb-96e8491ace80": {
      "main": [
        [
          {
            "node": "ff02026b-1083-47de-83cc-4809ecce14fb",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "bf9c87ee-9494-445a-9645-407de7a2b2e0": {
      "main": [
        [
          {
            "node": "4d99e350-ac86-4128-96d1-3e827a6a4b92",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "fb03a884-40fb-4622-8bed-a110616af897": {
      "main": [
        [
          {
            "node": "932eb48e-a0e7-43cd-b45b-f5ab0e38889b",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "eae2f1a4-9a71-422e-846d-ef9db7c7885b": {
      "main": [
        [
          {
            "node": "90f2c446-cad7-4249-b115-43db941fefb7",
            "type": "main",
            "index": 0
          },
          {
            "node": "dcc2314f-d7dd-4789-9318-6d70a2a67346",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a072d4af-4421-4c72-8a65-f85c9d195c33": {
      "main": [
        [
          {
            "node": "4c86423b-5357-4e8d-84cb-96e8491ace80",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7048dcfd-b13e-420e-805b-571213d55c66": {
      "main": [
        [
          {
            "node": "a072d4af-4421-4c72-8a65-f85c9d195c33",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Avancé - Création de contenu, IA Multimodale

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds32
Catégorie2
Types de nœuds9
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Geoffroy

Geoffroy

@jojoq42

Just a simple guy discovering the power of AI

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34