This workflow corresponds to n8n.io template #17064 — we link there as the canonical source.
This workflow follows the Form Trigger → Google Sheets 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": "LofomClskBHHb2aB",
"name": "AI ad factory with a Spendict quality gate (generate \u2192 gate \u2192 self-heal \u2192 log \u2192 notify)",
"tags": [],
"nodes": [
{
"id": "6fddee74-85ac-4f81-8f92-103086387b8d",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": 4,
"width": 520,
"height": 680,
"content": "# \ud83c\udfed AI ad factory, gated by Spendict\n\nGenerate ad variants with AI, let **Spendict** score each one, auto-fix the weak ones, and only log the winners \u2014 so your agent never funds a bad ad.\n\n## \u2699\ufe0f Setup\n1. **OpenAI** \u2014 Header Auth `Authorization: Bearer sk-\u2026` on both OpenAI nodes.\n2. **Spendict** \u2014 free key at spendict.com/dashboard \u2192 Header Auth `Authorization: Bearer spd_live_\u2026` on both Spendict nodes.\n3. **Google Sheets** \u2014 connect Google; pick a sheet with header columns: `status, headline, primary_text, platform, verdict, score, top_fix`.\n4. **Slack** \u2014 paste your Incoming Webhook URL into the **Slack: post summary** node.\n\n## \u25b6\ufe0f Run\nClick **Test workflow**, fill the form (product, platform, audience, count), and go. Swap the Form trigger for a Webhook / Airtable / Shopify trigger to run it on autopilot."
},
"typeVersion": 1
},
{
"id": "afd48032-77ba-4e8a-9553-e9258a6ef231",
"name": "Section: generate",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
288
],
"parameters": {
"color": 5,
"width": 620,
"height": 150,
"content": "### \u2460 Generate variants\nA form brief \u2192 **OpenAI** writes N distinct ad variants (headline + primary text) \u2192 we split them into one item per variant."
},
"typeVersion": 1
},
{
"id": "010be9a1-b56b-4369-8131-2e3d4a4df9c2",
"name": "Section: gate",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
288
],
"parameters": {
"color": 3,
"width": 460,
"height": 150,
"content": "### \u2461 Gate with Spendict\n**Spendict** scores each variant and returns a deterministic **run / fix_first / kill**. The Switch routes each ad by its verdict \u2014 the model can't green-light its own bad ad."
},
"typeVersion": 1
},
{
"id": "164c7038-3f43-45d0-88f3-27c1a79e0fe5",
"name": "Section: self-heal",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
1024
],
"parameters": {
"color": 6,
"width": 1100,
"height": 150,
"content": "### \u2462 Self-heal the fixable ones\n`fix_first` variants aren't discarded \u2014 the prioritized fixes are fed back to **OpenAI**, the ad is rewritten, and **Spendict re-scores** it. An automatic second chance."
},
"typeVersion": 1
},
{
"id": "28f777a1-a6fc-4f52-996b-a411a92b8433",
"name": "Section: log & notify",
"type": "n8n-nodes-base.stickyNote",
"position": [
2720,
288
],
"parameters": {
"color": 7,
"width": 860,
"height": 150,
"content": "### \u2463 Log winners & notify\nApproved ads (clean `run` + auto-fixed) are appended to **Google Sheets** and a summary is posted to **Slack**. Your shippable ads, in one place.\n\n_Sheets + Slack are optional \u2014 delete them and the core still runs._"
},
"typeVersion": 1
},
{
"id": "f0389346-2706-4c49-aa66-8f7c62a05f5a",
"name": "Section: killed",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
1008
],
"parameters": {
"color": 2,
"width": 380,
"height": 150,
"content": "### \u270b Killed ads\n`kill` verdicts are logged separately and never spent on \u2014 the whole point."
},
"typeVersion": 1
},
{
"id": "2d0f9713-bc51-43c9-80a6-2c0ce88c53b7",
"name": "Product brief",
"type": "n8n-nodes-base.formTrigger",
"position": [
672,
480
],
"parameters": {
"options": {},
"formTitle": "Generate + gate ad variants",
"formFields": {
"values": [
{
"fieldLabel": "product",
"placeholder": "e.g. Spendict \u2014 deterministic run/fix/kill verdicts for AI ad creative",
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "platform",
"fieldOptions": {
"values": [
{
"option": "meta"
},
{
"option": "tiktok"
},
{
"option": "google"
}
]
},
"requiredField": true
},
{
"fieldLabel": "audience",
"placeholder": "e.g. DTC founders running AI ad agents"
},
{
"fieldType": "number",
"fieldLabel": "count",
"placeholder": "5"
}
]
},
"formDescription": "Describe the offer. We'll generate ad variants and only surface the ones worth spending on."
},
"typeVersion": 2.2
},
{
"id": "55d1e2e5-f49e-4256-a5fc-64949cfe424e",
"name": "OpenAI: generate variants",
"type": "n8n-nodes-base.httpRequest",
"position": [
864,
480
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ model: 'gpt-4o-mini', response_format: { type: 'json_object' }, messages: [ { role: 'system', content: 'You are a senior direct-response performance marketer. Write scroll-stopping, specific ad copy that leads with a concrete pain or desire. Return ONLY JSON.' }, { role: 'user', content: 'Write ' + ($json.count || 5) + ' distinct ' + $json.platform + ' ad variants. Product/offer: ' + $json.product + '. Target audience: ' + ($json.audience || 'general') + '. Each variant needs a punchy headline and a 1-2 sentence primary_text. Return {\"variants\":[{\"headline\":\"...\",\"primary_text\":\"...\"}]}' } ] }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "255e76aa-53d5-4a3e-a883-e3d13642d8e6",
"name": "Split into variants",
"type": "n8n-nodes-base.code",
"position": [
1072,
480
],
"parameters": {
"jsCode": "const brief = $('Product brief').first().json;\nlet variants = [];\ntry {\n variants = JSON.parse($json.choices[0].message.content).variants || [];\n} catch (e) {\n throw new Error('Could not parse OpenAI response as JSON');\n}\nreturn variants.map((v) => ({\n json: {\n headline: v.headline || '',\n primary_text: v.primary_text || v.body || '',\n platform: brief.platform || 'meta',\n product_context: brief.product || '',\n target_audience: brief.audience || ''\n }\n}));"
},
"typeVersion": 2
},
{
"id": "da12129d-db6d-475b-9c82-c1d6b916db86",
"name": "Spendict: assess_ad_creative",
"type": "n8n-nodes-base.httpRequest",
"position": [
1280,
480
],
"parameters": {
"url": "https://www.spendict.com/api/v1/assess",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ ad_copy: { headline: $json.headline, primary_text: $json.primary_text }, platform: $json.platform, product_context: $json.product_context, target_audience: $json.target_audience }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "8009044c-d71e-44f0-84e6-526be8a40e52",
"name": "Gate on verdict",
"type": "n8n-nodes-base.switch",
"position": [
1488,
480
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "run",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.launch_recommendation }}",
"rightValue": "run"
}
]
},
"renameOutput": true
},
{
"outputKey": "fix_first",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.launch_recommendation }}",
"rightValue": "fix_first"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "kill"
}
},
"typeVersion": 3
},
{
"id": "6a5624ff-febb-4415-ab5d-81d4315f81d3",
"name": "Row: approved",
"type": "n8n-nodes-base.code",
"position": [
1712,
480
],
"parameters": {
"jsCode": "const ad = $('Split into variants').item.json;\nreturn [{ json: {\n status: 'approved',\n headline: ad.headline,\n primary_text: ad.primary_text,\n platform: ad.platform,\n verdict: $json.launch_recommendation,\n score: $json.overall_score,\n top_fix: ''\n} }];"
},
"typeVersion": 2
},
{
"id": "948b5358-3a8e-47c3-bbc3-49045bc6cedf",
"name": "OpenAI: rewrite with fixes",
"type": "n8n-nodes-base.httpRequest",
"position": [
1712,
832
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ model: 'gpt-4o-mini', response_format: { type: 'json_object' }, messages: [ { role: 'system', content: 'You are a senior direct-response performance marketer. Rewrite the ad to fix the listed problems, keeping it specific and scroll-stopping. Return ONLY JSON.' }, { role: 'user', content: 'Original headline: ' + $('Split into variants').item.json.headline + '. Original body: ' + $('Split into variants').item.json.primary_text + '. Fixes to apply: ' + JSON.stringify($json.prioritized_fixes) + '. Return {\"headline\":\"...\",\"primary_text\":\"...\"}' } ] }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "f0f3e195-bd58-480c-93de-59e333032362",
"name": "Parse rewritten ad",
"type": "n8n-nodes-base.code",
"position": [
1952,
832
],
"parameters": {
"jsCode": "const orig = $('Split into variants').item.json;\nlet fixed = {};\ntry { fixed = JSON.parse($json.choices[0].message.content); } catch (e) { fixed = {}; }\nreturn [{ json: {\n headline: fixed.headline || orig.headline,\n primary_text: fixed.primary_text || orig.primary_text,\n platform: orig.platform,\n product_context: orig.product_context,\n target_audience: orig.target_audience\n} }];"
},
"typeVersion": 2
},
{
"id": "57cc4d83-ec4c-4585-b232-460fc2c6ee56",
"name": "Spendict: re-assess",
"type": "n8n-nodes-base.httpRequest",
"position": [
2208,
832
],
"parameters": {
"url": "https://www.spendict.com/api/v1/assess",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ ad_copy: { headline: $json.headline, primary_text: $json.primary_text }, platform: $json.platform, product_context: $json.product_context, target_audience: $json.target_audience }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "e8a50e2c-b149-48ff-b6d2-bb7be0fbca0d",
"name": "Row: auto-fixed",
"type": "n8n-nodes-base.code",
"position": [
2464,
832
],
"parameters": {
"jsCode": "const ad = $('Parse rewritten ad').item.json;\nreturn [{ json: {\n status: 'auto-fixed',\n headline: ad.headline,\n primary_text: ad.primary_text,\n platform: ad.platform,\n verdict: $json.launch_recommendation,\n score: $json.overall_score,\n top_fix: ''\n} }];"
},
"typeVersion": 2
},
{
"id": "a1fa7d0e-c5f0-40cf-b639-3a3a91a31c7c",
"name": "Collect winners",
"type": "n8n-nodes-base.merge",
"position": [
2784,
480
],
"parameters": {},
"typeVersion": 3
},
{
"id": "7e9b32d0-a128-455d-9af8-cc6d6c869795",
"name": "Google Sheets: log approved",
"type": "n8n-nodes-base.googleSheets",
"position": [
2992,
480
],
"parameters": {
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.5
},
{
"id": "3c263a6d-c9f1-48e6-9972-249f5f639bea",
"name": "Approved ads",
"type": "n8n-nodes-base.aggregate",
"position": [
3184,
480
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "f1a01b1c-25db-4c43-9614-37e18b7c46e5",
"name": "Slack: post summary",
"type": "n8n-nodes-base.httpRequest",
"position": [
3392,
480
],
"parameters": {
"url": "https://hooks.slack.com/services/REPLACE_WITH_YOUR_SLACK_WEBHOOK",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ text: '\u2705 ' + ($json.data ? $json.data.length : 0) + ' ad(s) cleared Spendict and were logged to the sheet.' }) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "d96622b9-d940-4da1-b668-b34a8e462f10",
"name": "Killed ads (logged)",
"type": "n8n-nodes-base.aggregate",
"position": [
1264,
832
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "b762225e-b802-47a8-bb19-4b31ae596c20",
"nodeGroups": [],
"connections": {
"Approved ads": {
"main": [
[
{
"node": "Slack: post summary",
"type": "main",
"index": 0
}
]
]
},
"Product brief": {
"main": [
[
{
"node": "OpenAI: generate variants",
"type": "main",
"index": 0
}
]
]
},
"Row: approved": {
"main": [
[
{
"node": "Collect winners",
"type": "main",
"index": 0
}
]
]
},
"Collect winners": {
"main": [
[
{
"node": "Google Sheets: log approved",
"type": "main",
"index": 0
}
]
]
},
"Gate on verdict": {
"main": [
[
{
"node": "Row: approved",
"type": "main",
"index": 0
}
],
[
{
"node": "OpenAI: rewrite with fixes",
"type": "main",
"index": 0
}
],
[
{
"node": "Killed ads (logged)",
"type": "main",
"index": 0
}
]
]
},
"Row: auto-fixed": {
"main": [
[
{
"node": "Collect winners",
"type": "main",
"index": 1
}
]
]
},
"Parse rewritten ad": {
"main": [
[
{
"node": "Spendict: re-assess",
"type": "main",
"index": 0
}
]
]
},
"Spendict: re-assess": {
"main": [
[
{
"node": "Row: auto-fixed",
"type": "main",
"index": 0
}
]
]
},
"Split into variants": {
"main": [
[
{
"node": "Spendict: assess_ad_creative",
"type": "main",
"index": 0
}
]
]
},
"OpenAI: generate variants": {
"main": [
[
{
"node": "Split into variants",
"type": "main",
"index": 0
}
]
]
},
"OpenAI: rewrite with fixes": {
"main": [
[
{
"node": "Parse rewritten ad",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets: log approved": {
"main": [
[
{
"node": "Approved ads",
"type": "main",
"index": 0
}
]
]
},
"Spendict: assess_ad_creative": {
"main": [
[
{
"node": "Gate on verdict",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects a product brief via an n8n form, uses OpenAI to generate ad variants, gates each one with Spendict run/fix/kill scoring, auto-rewrites fixable ads, logs approved results to Google Sheets, and posts an approval count to Slack. Receives a product brief from…
Source: https://n8n.io/workflows/17064/ — 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.
This workflow collects a campaign brief via an n8n form, uses Spendict to generate a targeting strategy, uses OpenAI to draft platform-specific ad variants, then has Spendict score and gate each draft
Generate realistic, high-quality images from text prompts using the Flux AI Text-to-Image Generator API via RapidAPI, and seamlessly store the results in Google Drive and log them in Google Sheets — a
This workflow collects an ad campaign build brief via an n8n form, audits the structure with Spendict, and automatically rewrites failing builds with OpenAI before re-auditing, then logs the outcome t
This workflow hosts an n8n intake form, sends submissions to Anthropic Claude for practice-area classification and qualification, then routes results to Gmail and Google Sheets before returning a conf
This workflow collects SIP and mutual fund details via an n8n Form, calculates goal and performance metrics, generates a personalized advisory using OpenAI, emails a color-coded HTML report via Gmail,