{
  "id": "uqVvlBxWicEI55aK",
  "name": "Audit ad campaign structures before launch with Spendict, with AI auto-restructure",
  "tags": [],
  "nodes": [
    {
      "id": "26d7e621-1b41-4eb6-a0c2-0a6345df39f2",
      "name": "Campaign build brief",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        576,
        272
      ],
      "parameters": {
        "options": {},
        "formTitle": "Audit a campaign build",
        "formFields": {
          "values": [
            {
              "fieldType": "dropdown",
              "fieldLabel": "platform",
              "fieldOptions": {
                "values": [
                  {
                    "option": "meta"
                  },
                  {
                    "option": "google"
                  },
                  {
                    "option": "tiktok"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "objective",
              "placeholder": "e.g. purchases, leads, app installs",
              "requiredField": true
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "campaign_structure",
              "placeholder": "e.g. 1 campaign (CBO $100/day) \u2192 3 ad sets: broad, lookalike 1%, retargeting 30d \u2192 2 ads each\u2026",
              "requiredField": true
            },
            {
              "fieldLabel": "total_budget",
              "placeholder": "e.g. $3,000/month (optional)"
            },
            {
              "fieldLabel": "funnel_stage",
              "placeholder": "prospecting / retargeting / retention (optional)"
            }
          ]
        },
        "formDescription": "Describe the campaign structure you're about to build. Spendict audits it against the platform's rulebook before any budget moves."
      },
      "typeVersion": 2.2
    },
    {
      "id": "27aafd8b-c418-4265-afc1-903a692a8421",
      "name": "Spendict: audit_campaign_structure",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        816,
        272
      ],
      "parameters": {
        "url": "https://www.spendict.com/api/v1/audit-campaign",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ platform: $json.platform, objective: $json.objective, campaign_structure: $json.campaign_structure, total_budget: $json.total_budget || undefined, funnel_stage: $json.funnel_stage || undefined }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "a23e4345-9a21-44a0-bf1c-0ed29d1bce15",
      "name": "Route on verdict",
      "type": "n8n-nodes-base.switch",
      "position": [
        1040,
        272
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "sound",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.launch_recommendation }}",
                    "rightValue": "sound"
                  }
                ]
              },
              "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": "restructure"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "6afd1dc6-fb5a-4eba-8663-6a2426b2d798",
      "name": "Row: sound",
      "type": "n8n-nodes-base.code",
      "position": [
        1296,
        272
      ],
      "parameters": {
        "jsCode": "const brief = $('Campaign build brief').first().json;\nreturn [{ json: {\n  status: 'sound as submitted',\n  platform: brief.platform,\n  objective: brief.objective,\n  verdict: $json.launch_recommendation,\n  score: $json.overall_score,\n  structure: brief.campaign_structure,\n  top_fix: ''\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b44f295e-d05f-40a1-b74e-3a3a66d970da",
      "name": "OpenAI: restructure",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        672
      ],
      "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 media buyer. Rewrite the campaign structure to resolve every listed problem while keeping the objective and budget realistic. Describe the revised build in the same free-text style. Return ONLY JSON.' }, { role: 'user', content: 'Platform: ' + $('Campaign build brief').first().json.platform + '. Objective: ' + $('Campaign build brief').first().json.objective + '. Original structure: ' + $('Campaign build brief').first().json.campaign_structure + '. Problems to fix: ' + JSON.stringify($json.prioritized_fixes) + '. Return {\"campaign_structure\":\"...\"}' } ] }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "2929d9dc-9cea-489e-9fac-bc307f78ce9e",
      "name": "Parse revised structure",
      "type": "n8n-nodes-base.code",
      "position": [
        1536,
        672
      ],
      "parameters": {
        "jsCode": "const brief = $('Campaign build brief').first().json;\nlet revised = '';\ntry {\n  revised = JSON.parse($json.choices[0].message.content).campaign_structure || '';\n} catch (e) {\n  revised = '';\n}\nif (!revised) {\n  throw new Error('Could not parse the restructured build from OpenAI');\n}\nreturn [{ json: {\n  platform: brief.platform,\n  objective: brief.objective,\n  campaign_structure: revised,\n  total_budget: brief.total_budget || '',\n  funnel_stage: brief.funnel_stage || ''\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "04e6a91c-2e2c-46a7-a588-99881a3fdccb",
      "name": "Spendict: re-audit",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1760,
        672
      ],
      "parameters": {
        "url": "https://www.spendict.com/api/v1/audit-campaign",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ platform: $json.platform, objective: $json.objective, campaign_structure: $json.campaign_structure, total_budget: $json.total_budget || undefined, funnel_stage: $json.funnel_stage || undefined }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "4ae3a215-82ec-48d2-92d2-f32022178dfa",
      "name": "Row: restructured",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        672
      ],
      "parameters": {
        "jsCode": "const revised = $('Parse revised structure').item.json;\nconst appliedFixes = $('Spendict: audit_campaign_structure').first().json.prioritized_fixes || [];\nreturn [{ json: {\n  status: 'auto-restructured',\n  platform: revised.platform,\n  objective: revised.objective,\n  verdict: $json.launch_recommendation,\n  score: $json.overall_score,\n  structure: revised.campaign_structure,\n  top_fix: appliedFixes.length ? (appliedFixes[0].suggestion || appliedFixes[0].issue || '') : ''\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "3c5b9a19-b40c-475d-95af-d0b517f83e6d",
      "name": "Collect results",
      "type": "n8n-nodes-base.merge",
      "position": [
        2016,
        272
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "57991d2d-0ddf-4050-9e09-85ad35086a2b",
      "name": "Google Sheets: audit log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2256,
        272
      ],
      "parameters": {
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "47f9d558-8689-46f5-b55f-ba537d64509b",
      "name": "Slack: audit result",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2480,
        272
      ],
      "parameters": {
        "url": "https://hooks.slack.com/services/REPLACE_WITH_YOUR_SLACK_WEBHOOK",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ text: '\ud83e\uddf1 Structure audit: ' + $json.status + ' \u2014 verdict ' + $json.verdict + ' (' + $json.score + '/100)' + ($json.top_fix ? '. Top fix applied: ' + $json.top_fix : '') }) }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "19e53719-9232-4c06-a476-ee0a6ee9cd6b",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -32
      ],
      "parameters": {
        "width": 480,
        "height": 960,
        "content": "## Audit campaign structure before launch\n\nPaste a campaign build into a form and Spendict audits it against the platform's rulebook before any budget moves \u2014 then has OpenAI rebuild the ones that fail and re-audits the result.\n\n### How it works\n\n1. A form takes the campaign build as free text: structure, budgets, audiences and bid strategy.\n2. Spendict's `audit_campaign_structure` judges it against the correct platform's rules and returns a verdict with prioritized fixes.\n3. A Switch routes on that verdict. A `sound` build passes straight to the log, ready to build as submitted.\n4. `fix_first` and `restructure` builds go to OpenAI along with Spendict's fixes; it rewrites the structure and Spendict re-audits the revision.\n5. Every audited build \u2014 original or rebuilt \u2014 is appended to Google Sheets and the verdict is posted to Slack.\n\n### Setup\n\n- **Spendict** \u2014 free key at spendict.com/dashboard, then Header Auth `Authorization: Bearer spd_live_\u2026` on both Spendict nodes.\n- **OpenAI** \u2014 Header Auth `Authorization: Bearer sk-\u2026` on the OpenAI node.\n- **Google Sheets** \u2014 connect Google and pick a sheet with headers `platform`, `objective`, `verdict`, `score`, `primary_leak`, `structure`.\n- **Slack** \u2014 paste your Incoming Webhook URL into the Slack node.\n\n### Customization tips\n\nPoint the form at your own build template, or swap it for a webhook fed by your campaign planner."
      },
      "typeVersion": 1
    },
    {
      "id": "7b42c7ba-4c26-4a1c-9550-83d744489931",
      "name": "Section: submit and audit",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 1140,
        "height": 524,
        "content": "## Submit and audit build\n\nA form takes the build as free text. Spendict audits it against the platform rulebook and routes on the verdict; sound builds pass straight to the log."
      },
      "typeVersion": 1
    },
    {
      "id": "83c6e1bb-a6f2-4052-8862-a1ebc8e813ed",
      "name": "Section: auto-restructure",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 940,
        "height": 412,
        "content": "\n\n\n\n\n\n\n\n\n\n\n## Auto-restructure the failures\n\nFailing builds go to OpenAI with Spendict's fixes, get rewritten, and are re-audited before they are accepted."
      },
      "typeVersion": 1
    },
    {
      "id": "7ebb2379-7c69-42f9-a8c2-1f608c444ddc",
      "name": "Section: log and notify",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1936,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 732,
        "height": 524,
        "content": "## Log and notify\n\nEvery audited build \u2014 sound or rebuilt \u2014 is appended to Google Sheets and the verdict is posted to Slack."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "cf2977f0-fab5-4768-a89c-518ec54e7577",
  "nodeGroups": [],
  "connections": {
    "Row: sound": {
      "main": [
        [
          {
            "node": "Collect results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect results": {
      "main": [
        [
          {
            "node": "Google Sheets: audit log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route on verdict": {
      "main": [
        [
          {
            "node": "Row: sound",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAI: restructure",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAI: restructure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Row: restructured": {
      "main": [
        [
          {
            "node": "Collect results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Spendict: re-audit": {
      "main": [
        [
          {
            "node": "Row: restructured",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI: restructure": {
      "main": [
        [
          {
            "node": "Parse revised structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Campaign build brief": {
      "main": [
        [
          {
            "node": "Spendict: audit_campaign_structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse revised structure": {
      "main": [
        [
          {
            "node": "Spendict: re-audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets: audit log": {
      "main": [
        [
          {
            "node": "Slack: audit result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Spendict: audit_campaign_structure": {
      "main": [
        [
          {
            "node": "Route on verdict",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}