01 AI 미디어 바이어를 사용한 Facebook 광고 성과 분석 및 Google Sheets로 인사이트 전송
고급
이것은Market Research, AI Summarization분야의자동화 워크플로우로, 34개의 노드를 포함합니다.주로 If, Set, Code, Sort, Merge 등의 노드를 사용하며. Gemini AI를 사용한 Facebook 광고 분석 및 Google Sheets로 인사이트 전송
사전 요구사항
- •대상 API의 인증 정보가 필요할 수 있음
- •Google Sheets API 인증 정보
- •Google Gemini API Key
사용된 노드 (34)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"id": "e1a3hFH38UDCqquQ",
"meta": {
"instanceId": "ba66afb641da72f937c53df420ad492fd21f2a9f2c92857275062b3d7831e1b1"
},
"name": "01 Analyze Facebook Ad Performance with an AI Media Buyer and Send Insights to Google Sheets",
"tags": [],
"nodes": [
{
"id": "ce48c496-4d57-41c3-992e-046c1da7d6f6",
"name": "'워크플로 테스트' 클릭 시",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-3184,
768
],
"parameters": {},
"typeVersion": 1
},
{
"id": "89a2936e-7719-4cd8-8638-7db0d13e1a3f",
"name": "모든 데이터 문자열화",
"type": "n8n-nodes-base.code",
"position": [
-96,
448
],
"parameters": {
"jsCode": "// --- START OF CODE NODE 4 SCRIPT (Stringify Output) ---\n// Mode: Run Once for All Items\n// Input: The array of aggregated ad creative performance objects\n\nconst allAggregatedItems = $input.all(); // This is an array of n8n items\n\n// Extract the .json part from each n8n item to get the actual data objects\nconst dataToConvert = allAggregatedItems.map(item => item.json);\n\n// Stringify the array of data objects\n// The 'null, 2' arguments pretty-print the JSON string with an indent of 2 spaces,\n// which can be helpful for readability if you're inspecting it, but not strictly necessary for an LLM.\n// If the LLM prefers a more compact JSON, you can omit 'null, 2'.\nconst jsonString = JSON.stringify(dataToConvert, null, 2);\n\n// Output a single item containing this string\nreturn [{ json: { all_ads_data_string: jsonString } }];\n// --- END OF CODE NODE 4 SCRIPT ---"
},
"typeVersion": 2
},
{
"id": "48d96bc5-b537-4fb0-9f09-e8c75f57a1c2",
"name": "Google Gemini 채팅 모델",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
560,
720
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-pro-preview-06-05"
},
"credentials": {
"googlePalmApi": {
"id": "T2fKW04zFKdQvjyO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "e5ce198d-0740-456f-9160-6f7b253cdbe0",
"name": "구조화된 출력 파서",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
752,
720
],
"parameters": {
"jsonSchemaExample": "[\n {\n \"ad_id\": \"XXX\",\n \"ad_name\": \"Insert Ad Name\",\n \"total_spend_creative\": 490.01,\n \"performance_category\": \"HELL YES\",\n \"justification\": \"This ad is a 'HELL YES' performer. With a significant spend of $490.01 driving 93 purchases, its ROAS of 19.82 massively outperforms the benchmark ROAS of 8.5. Furthermore, its Cost Per Purchase of $5.27 is well below the benchmark $7.50, and its 5.26% conversion rate is substantially higher than the benchmark 3.1%. The combination of high volume, exceptional ROAS, and efficiency makes it a clear top driver.\",\n \"key_performance_indicators\": {\n \"ad_spend\": 490.01,\n \"ad_total_purchases\": 93,\n \"ad_total_purchase_value\": 9709.6,\n \"ad_roas\": 19.82,\n \"benchmark_roas\": 8.5,\n \"ad_conversion_rate\": \"5.26%\",\n \"benchmark_conversion_rate\": \"3.1%\",\n \"ad_cost_per_purchase\": 5.27,\n \"benchmark_cost_per_purchase\": 7.50\n },\n \"recommendation\": \"Aggressively Scale Budget & Explore New Audiences\"\n }\n]"
},
"typeVersion": 1.2
},
{
"id": "733a2556-a9d9-4892-9acb-6fcdb32a326e",
"name": "데이터 분할",
"type": "n8n-nodes-base.splitOut",
"position": [
944,
528
],
"parameters": {
"options": {},
"fieldToSplitOut": "output"
},
"typeVersion": 1
},
{
"id": "aa9a05cc-532f-4f6f-844b-5511178b1722",
"name": "원시 데이터를 Google 시트로 전송",
"type": "n8n-nodes-base.googleSheets",
"position": [
272,
-16
],
"parameters": {
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "XXXX"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "8dzgEvzMeMhDaVGY",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "0cbd26d6-c758-47f8-bca1-190d4af58626",
"name": "스티키 노트1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2992,
448
],
"parameters": {
"color": 4,
"width": 1740,
"height": 756,
"content": "### Step 1: Securely Manage Your Facebook API Token\n\nThis section retrieves your long-term access token and automatically refreshes it if it's about to expire, ensuring the workflow always has valid credentials.\n\n**➡️ Action Required:**\n- Configure the NocoDB nodes with your database details.\n- Alternatively, replace this entire section with your preferred credential management system (e.g., n8n's own credential store)."
},
"typeVersion": 1
},
{
"id": "7b2463ab-0eac-4bd5-8185-744ea231ef1d",
"name": "장기 토큰 가져오기",
"type": "n8n-nodes-base.nocoDb",
"position": [
-2928,
768
],
"parameters": {
"table": "mlkymmbtoa2iz72",
"options": {},
"operation": "getAll",
"projectId": "pfhk9mz7b66t5s2",
"authentication": "nocoDbApiToken"
},
"credentials": {
"nocoDbApiToken": {
"id": "J7sjAo2FWhiSTa4M",
"name": "NocoDB Token account"
}
},
"typeVersion": 3
},
{
"id": "e0d95e0a-4cc2-42a7-9f6d-337bb1dc68b9",
"name": "토큰 갱신이 필요한가?",
"type": "n8n-nodes-base.code",
"position": [
-2704,
768
],
"parameters": {
"jsCode": "// Get the first input item (assumes NocoDB returns an array)\nconst tokenData = $input.first()?.json || {};\n\n// Get current time in milliseconds\nconst now = new Date().getTime();\n\n// Parse the token's end date from NocoDB\nconst endDate = new Date(tokenData.end_date).getTime();\n\n// Define 3 days in milliseconds\nconst threeDaysInMs = 3 * 24 * 60 * 60 * 1000;\n\n// Calculate the difference between end date and now\nconst timeToExpiry = endDate - now;\n\n// Determine if the token is within 3 days of expiry and not already expired\nconst needsRefresh = timeToExpiry > 0 && timeToExpiry <= threeDaysInMs;\n\n// Return both the result and debug info\nreturn [{\n json: {\n needs_refresh: needsRefresh,\n time_to_expiry_ms: timeToExpiry,\n expires_at: tokenData.end_date,\n checked_at: new Date().toISOString(),\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "5f27af56-537f-42f3-9c11-52189918c631",
"name": "장기 액세스 토큰 가져오기1",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2240,
704
],
"parameters": {
"url": "https://graph.facebook.com/v22.0/oauth/access_token",
"method": "POST",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "grant_type",
"value": "fb_exchange_token"
},
{
"name": "client_id"
},
{
"name": "client_secret"
},
{
"name": "fb_exchange_token",
"value": "="
}
]
}
},
"typeVersion": 4.2
},
{
"id": "72f99e3f-1c6c-4186-b0d8-1c10d5c32fa7",
"name": "토큰1의 만료일 계산",
"type": "n8n-nodes-base.code",
"position": [
-2016,
704
],
"parameters": {
"jsCode": "// Input from the previous node\nconst input = $input.first();\nconst tokenData = input.json || {};\n\n// Current time in milliseconds\nconst now = new Date().getTime();\n\n// Token details from input\nconst accessToken = tokenData.access_token || '';\nconst expiresIn = tokenData.expires_in || 5184000; // Default to 60 days (5184000 seconds) if not provided\nconst issuedAt = tokenData.issued_at ? new Date(tokenData.issued_at).getTime() : now; // Use now if issued_at is missing\nconst expiryTime = issuedAt + (expiresIn * 1000); // Convert expires_in to milliseconds\nconst endDate = new Date(expiryTime).toISOString(); // Calculated end date\n\n// Output the end date and access token\nreturn [{\n json: {\n access_token: accessToken,\n end_date: endDate,\n current_time: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c53eca75-6433-46fb-95ac-3b85a1b6420a",
"name": "토큰 업데이트",
"type": "n8n-nodes-base.nocoDb",
"position": [
-1824,
704
],
"parameters": {
"table": "mlkymmbtoa2iz72",
"fieldsUi": {
"fieldValues": [
{
"fieldName": "=Id",
"fieldValue": "={{ $('Getting Long-Term Token').item.json.Id }}"
},
{
"fieldName": "longTermAccessToken",
"fieldValue": "={{ $json.access_token }}"
},
{
"fieldName": "end_date",
"fieldValue": "={{ $json.end_date }}"
},
{
"fieldName": "current_time",
"fieldValue": "={{ $json.current_time }}"
}
]
},
"operation": "update",
"projectId": "pfhk9mz7b66t5s2",
"authentication": "nocoDbApiToken"
},
"credentials": {
"nocoDbApiToken": {
"id": "J7sjAo2FWhiSTa4M",
"name": "NocoDB Token account"
}
},
"typeVersion": 3
},
{
"id": "d82dc7c6-9dc2-427d-9164-0073ee5b611d",
"name": "스티키 노트2",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
-352
],
"parameters": {
"width": 752,
"height": 540,
"content": "### Step 3a: Log Processed Data to Google Sheets\n\nThis node takes the cleaned and calculated performance data for each ad creative and sends it to your Google Sheet.\n\n**Why this step is important:**\nIt creates a complete record of your ad metrics and populates the rows that the AI will analyze and update in a later step.\n\n**➡️ Action Required:**\n- **Connect Credentials**: Ensure your Google Sheets account is connected.\n- **Document ID**: Replace `XXXX` in the *Document ID* field with the ID of your Google Sheet (found in the URL).\n- **Sheet Name**: Select the correct sheet where the data should be sent."
},
"typeVersion": 1
},
{
"id": "fe5643c3-3012-4573-84b7-d11c93261bee",
"name": "장기 토큰 가져오기1",
"type": "n8n-nodes-base.set",
"position": [
-1872,
976
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f3604846-252c-438b-8266-36ec0819d3fa",
"name": "longAccessToken",
"type": "string",
"value": "={{ $('Getting Long-Term Token').item.json.longTermAccessToken }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ce1cff8f-e076-4ec5-9a86-58db01605894",
"name": "테이블 형식으로 분할",
"type": "n8n-nodes-base.code",
"position": [
-896,
528
],
"parameters": {
"jsCode": "// --- START OF CODE NODE 1 SCRIPT (Process Raw Data) ---\n// Mode: Run Once for All Items\n\nconst allN8NInputItems = $input.all(); // Gets all n8n items (usually just one from HTTP Request if pagination combines, or one per page)\nconst finalOutputItems = [];\n\n// Iterate over each n8n item received (typically one, or one per page if HTTP node outputs that way)\nfor (const n8nItem of allN8NInputItems) {\n const facebookResponse = n8nItem.json; // This is an object like: { \"data\": [...], \"paging\": ... }\n\n // Check if the 'data' array exists in the Facebook response\n if (facebookResponse && facebookResponse.data && Array.isArray(facebookResponse.data)) {\n const adsInsightsArray = facebookResponse.data; // This is the array of actual ad insight objects\n\n // Loop through each ad insight object within the 'data' array\n for (const fbData of adsInsightsArray) {\n const output = {};\n\n // Basic Info\n output.ad_id = fbData.ad_id; // <<< --- ADDING AD_ID HERE ---\n output.date_start = fbData.date_start;\n output.date_stop = fbData.date_stop;\n output.campaign_name = fbData.campaign_name;\n output.adset_name = fbData.adset_name;\n output.ad_name = fbData.ad_name;\n output.objective = fbData.objective;\n output.spend = parseFloat(fbData.spend || 0);\n output.impressions = parseInt(fbData.impressions || 0);\n output.clicks = parseInt(fbData.clicks || 0);\n\n // Initialize specific e-commerce metrics\n output.add_to_carts_count = 0;\n output.checkouts_initiated_count = 0;\n output.purchases_count = 0;\n output.purchase_value_total = 0.0;\n\n // --- Define your PREFERRED canonical action_types in order of preference ---\n const PREFERRED_ACTION_ORDER = {\n atc: ['omni_add_to_cart', 'offsite_conversion.fb_pixel_add_to_cart', 'add_to_cart', 'onsite_web_app_add_to_cart', 'onsite_web_add_to_cart'],\n checkout: ['omni_initiated_checkout', 'offsite_conversion.fb_pixel_initiate_checkout', 'initiate_checkout', 'onsite_web_initiate_checkout'],\n purchase_count: ['omni_purchase', 'offsite_conversion.fb_pixel_purchase', 'purchase', 'web_in_store_purchase', 'onsite_web_purchase', 'onsite_web_app_purchase'],\n purchase_value: ['omni_purchase', 'offsite_conversion.fb_pixel_purchase', 'purchase', 'web_in_store_purchase', 'onsite_web_purchase', 'onsite_web_app_purchase']\n };\n\n // --- Helper function to get COUNT for a specific metric from 'actions' ---\n function getActionCount(actionsData, preferredTypes) {\n if (!actionsData) return 0;\n let actionsArray = [];\n if (Array.isArray(actionsData)) {\n actionsArray = actionsData;\n } else if (typeof actionsData === 'object' && actionsData !== null && typeof actionsData.action_type !== 'undefined') {\n actionsArray = [actionsData]; // Handle if 'actions' is a single object\n } else {\n return 0; // Invalid actionsData structure or empty\n }\n\n for (const type of preferredTypes) {\n const action = actionsArray.find(a => a.action_type === type);\n if (action) {\n return parseInt(action.value || 0);\n }\n }\n return 0;\n }\n\n // --- Helper function to get MONETARY VALUE for purchases from 'action_values' ---\n function getPurchaseMonetaryValue(actionValuesData, preferredTypes) {\n if (!actionValuesData || !Array.isArray(actionValuesData)) return 0.0;\n for (const type of preferredTypes) {\n const action = actionValuesData.find(a => a.action_type === type);\n if (action) {\n if (type === 'web_app_in_store_purchase' && parseFloat(action.value || 0) < 1.00) {\n const hasOtherMoreValuablePurchaseType = preferredTypes.some(pt =>\n pt !== 'web_app_in_store_purchase' &&\n actionValuesData.find(av => av.action_type === pt && parseFloat(av.value || 0) >= 1.00)\n );\n if (hasOtherMoreValuablePurchaseType) {\n continue;\n }\n }\n return parseFloat(action.value || 0);\n }\n }\n return 0.0;\n }\n\n // Populate the e-commerce metrics\n output.add_to_carts_count = getActionCount(fbData.actions, PREFERRED_ACTION_ORDER.atc);\n output.checkouts_initiated_count = getActionCount(fbData.actions, PREFERRED_ACTION_ORDER.checkout);\n output.purchases_count = getActionCount(fbData.actions, PREFERRED_ACTION_ORDER.purchase_count);\n output.purchase_value_total = getPurchaseMonetaryValue(fbData.action_values, PREFERRED_ACTION_ORDER.purchase_value);\n \n finalOutputItems.push({ json: output });\n }\n } else {\n console.error(\"Input data structure is not as expected from HTTP Request. 'data' array not found or not an array.\", facebookResponse);\n finalOutputItems.push({ json: { error: \"Facebook response parsing error\", details: \"Input 'data' array not found.\" } });\n }\n}\n\nreturn finalOutputItems;\n// --- END OF CODE NODE 1 SCRIPT (Process Raw Data) ---"
},
"typeVersion": 2
},
{
"id": "83a6b0a8-8422-4705-9576-deca22f7f092",
"name": "광고비 기준 정렬",
"type": "n8n-nodes-base.sort",
"position": [
-304,
448
],
"parameters": {
"options": {},
"sortFieldsUi": {
"sortField": [
{
"order": "descending",
"fieldName": "total_spend"
}
]
}
},
"typeVersion": 1
},
{
"id": "c8bda278-bb77-423b-b2bc-49f23d6d3eb5",
"name": "스티키 노트3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1072,
272
],
"parameters": {
"width": 512,
"height": 620,
"content": "### Step 6: Update Google Sheets with AI Insights\n\nThis final step takes the AI's analysis for each ad and updates your Google Sheet, matching the ad by its unique `ad_id`.\n\n**➡️ Action Required:**\n- Configure both Google Sheets nodes (**Sending Raw Data To A Google Sheet** and this one) with your **Google Sheet ID**.\n- The first node populates the sheet with raw data, and this node adds the AI analysis columns."
},
"typeVersion": 1
},
{
"id": "fdb93589-5381-4dfc-a0ec-7dbd01b2bc64",
"name": "스티키 노트4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1232,
272
],
"parameters": {
"color": 3,
"width": 320,
"height": 524,
"content": "### Step 2: Fetch Facebook Ad Data\n\nThis node calls the Facebook Graph API to get performance data for all your ads from the last 28 days.\n\n**➡️ Action Required:**\n- In the URL parameter, replace `act_XXXXXX` with your **Facebook Ad Account ID**."
},
"typeVersion": 1
},
{
"id": "5b88a75e-945c-46b2-92d0-a8ddd399433a",
"name": "스티키 노트6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-592,
208
],
"parameters": {
"width": 680,
"height": 744,
"content": "### Step 3: Calculate Ad & Benchmark KPIs\n\nThe workflow splits here to perform two crucial calculations in parallel:\n- **Top Path**: Calculates performance metrics for each *individual ad creative*.\n- **Bottom Path**: Calculates the *overall account average* for all sales campaigns to use as a benchmark.\n\nThis comparison is the core of the AI analysis."
},
"typeVersion": 1
},
{
"id": "2fdd3e2f-92fa-4034-841a-500dad9f91bc",
"name": "판매 캠페인만 필터링",
"type": "n8n-nodes-base.filter",
"position": [
-704,
528
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7e4103af-52c4-4035-97e5-4f4356312ed6",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.objective }}",
"rightValue": "OUTCOME_SALES"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b2f42335-8b1e-4d00-a9a8-f7f631f1927d",
"name": "지난 28일간 캠페인/광고세트/광고 단위 데이터 가져오기",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1120,
528
],
"parameters": {
"url": "=https://graph.facebook.com/v22.0/act_XXXXXX/insights",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "level",
"value": "ad"
},
{
"name": "fields",
"value": "campaign_name,adset_name,ad_name,ad_id,objective,spend,impressions,clicks,actions,action_values,date_start,date_stop"
},
{
"name": "date_preset",
"value": "=last_28d"
},
{
"name": "limit",
"value": "500"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.accessToken }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "2dfd3d0c-0988-42df-8dfc-c545188d4ea2",
"name": "시니어 Facebook 광고 미디어 바이어",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
608,
528
],
"parameters": {
"text": "=**Role:** You are a Senior Facebook Ads Media Buyer with extensive experience in e-commerce campaign optimization. Your primary goal is to maximize Return on Ad Spend (ROAS) and overall profitability by identifying high-impact ad creatives and categorizing their performance.\n\n**Context:**\nYou are analyzing ad creative performance for an e-commerce client. You have been provided with two sets of data for the past 14 days:\n1. **\"Ad Creative Performance Data\"**: A JSON string representing an array of objects. Each object details a unique ad creative and its performance metrics (including `total_spend`, `total_purchases`, `total_purchase_value`).\n2. **\"Overall Account Benchmark Data\"**: A JSON string representing a single object. This object contains the aggregated performance and KPIs for all ads in the account over the same period, serving as crucial benchmark values.\n\n**Task:**\nYour task is to rigorously evaluate each ad creative from the \"Ad Creative Performance Data\" against the \"Overall Account Benchmark Data.\" **For every ad creative you evaluate, you MUST compare its key calculated metrics (CTR, CPC, CPM, Cost Per Add To Cart, Cost Per Checkout, Cost Per Purchase, Conversion Rate, ROAS, Average Order Value) directly against the corresponding values in the benchmark data.**\n\nBased on this comparison and the spend rules below, categorize the performance of **each ad creative**.\n\n**Performance Categorization Rules & Spend Thresholds:**\n\n1. **Spend Significance:**\n * Any ad creative with a `total_spend` of **less than $50** CANNOT be categorized as \"HELL YES\", \"YES\", or even \"MAYBE\", regardless of its other metrics. At best, it can be \"NOT REALLY\" if ratios are poor, or \"INSUFFICIENT DATA/SPEND\" if ratios look promising but spend is too low for a definitive positive categorization.\n * To be considered for \"HELL YES\" or \"YES\" categories, an ad creative **MUST have a `total_spend` of $100 or more.**\n\n2. **Performance Categories (evaluate against benchmarks AFTER spend rules):**\n * **\"HELL YES\":** Significant spend (>= $100) AND dramatically outperforms benchmarks across ROAS, CVR, and Cost Per Purchase. Clear 80/20 driver.\n * **\"YES\":** Significant spend (>= $100) AND clearly outperforms benchmarks on ROAS, CVR, and Cost Per Purchase. Reliably contributing.\n * **\"MAYBE\":** Shows promise or mixed results. Spend >= $50. Marginally outperforms benchmarks, or strong on some key metrics but not others. Or, spend < $100 but > $50 with very strong ratios. Requires observation/optimization.\n * **\"NOT REALLY\":** Underperforming. Metrics generally at/below benchmarks. Or, spend < $50 with unpromising ratios. Not a clear winner.\n * **\"WE WASTED MONEY\":** Performing very poorly. Likely spent > $30-$50 (use judgment) with significantly worse metrics than benchmarks (very low ROAS, very high CPP). Pause candidate.\n * **\"INSUFFICIENT DATA/SPEND\":** Spend < $50. Ratios might look okay/good, but not enough data for confident positive categorization (\"MAYBE\", \"YES\", \"HELL YES\").\n\n**Input Data:**\n\n**1. Ad Creative Performance Data (JSON String - LLM needs to parse this):**\n\n{{ $json.data[0].all_ads_data_string }}\n\n2. Overall Account Benchmark Data (JSON String - LLM needs to parse this):\n{{ $json.data[1].benchmarkData }}\n",
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.6
},
{
"id": "6256c78d-67fb-4bc1-afb9-07b6191e254d",
"name": "광고 데이터와 벤치마킹 데이터 결합",
"type": "n8n-nodes-base.merge",
"position": [
224,
512
],
"parameters": {},
"typeVersion": 3.1
},
{
"id": "88276317-cd5c-4529-8329-51f1fc9bcfb0",
"name": "스티키 노트7",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
272
],
"parameters": {
"color": 3,
"width": 356,
"height": 544,
"content": "### Step 4: Prepare Data for AI Analysis\n\nThese nodes take the individual ad data and the overall benchmark data, convert them into clean JSON strings, and merge them together. This prepares a complete package of information to be sent to the AI in a single, context-rich prompt."
},
"typeVersion": 1
},
{
"id": "f44bb155-47a1-422d-b857-6007bbcb9844",
"name": "Google 시트에 광고 인사이트 업데이트",
"type": "n8n-nodes-base.googleSheets",
"position": [
1184,
528
],
"parameters": {
"columns": {
"value": {
"ad_id": "={{ $json.ad_id }}",
"Justification": "={{ $json.justification }}",
"Recommendation": "={{ $json.recommendation }}",
"Best Performing Ad": "={{ $json.performance_category }}"
},
"schema": [
{
"id": "ad_name",
"type": "string",
"display": true,
"required": false,
"displayName": "ad_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ad_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ad_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "objective",
"type": "string",
"display": true,
"required": false,
"displayName": "objective",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_spend",
"type": "string",
"display": true,
"required": false,
"displayName": "total_spend",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_impressions",
"type": "string",
"display": true,
"required": false,
"displayName": "total_impressions",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_clicks",
"type": "string",
"display": true,
"required": false,
"displayName": "total_clicks",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_add_to_carts",
"type": "string",
"display": true,
"required": false,
"displayName": "total_add_to_carts",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_checkouts_initiated",
"type": "string",
"display": true,
"required": false,
"displayName": "total_checkouts_initiated",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_purchases",
"type": "string",
"display": true,
"required": false,
"displayName": "total_purchases",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_purchase_value",
"type": "string",
"display": true,
"required": false,
"displayName": "total_purchase_value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ctr",
"type": "string",
"display": true,
"required": false,
"displayName": "ctr",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cpc",
"type": "string",
"display": true,
"required": false,
"displayName": "cpc",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cpm",
"type": "string",
"display": true,
"required": false,
"displayName": "cpm",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cost_per_add_to_cart",
"type": "string",
"display": true,
"required": false,
"displayName": "cost_per_add_to_cart",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cost_per_checkout",
"type": "string",
"display": true,
"required": false,
"displayName": "cost_per_checkout",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cost_per_purchase",
"type": "string",
"display": true,
"required": false,
"displayName": "cost_per_purchase",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "roas",
"type": "string",
"display": true,
"required": false,
"displayName": "roas",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "average_order_value",
"type": "string",
"display": true,
"required": false,
"displayName": "average_order_value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "conversion_rate",
"type": "string",
"display": true,
"required": false,
"displayName": "conversion_rate",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Best Performing Ad",
"type": "string",
"display": true,
"required": false,
"displayName": "Best Performing Ad",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Justification",
"type": "string",
"display": true,
"required": false,
"displayName": "Justification",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Recommendation",
"type": "string",
"display": true,
"required": false,
"displayName": "Recommendation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source_URL",
"type": "string",
"display": true,
"required": false,
"displayName": "Source_URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Asset Type",
"type": "string",
"display": true,
"required": false,
"displayName": "Asset Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Transcription",
"type": "string",
"display": true,
"required": false,
"displayName": "Transcription",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hook",
"type": "string",
"display": true,
"required": false,
"displayName": "hook",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "purpose",
"type": "string",
"display": true,
"required": false,
"displayName": "purpose",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "text_captions",
"type": "string",
"display": true,
"required": false,
"displayName": "text_captions",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "disclaimer",
"type": "string",
"display": true,
"required": false,
"displayName": "disclaimer",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "focal_point",
"type": "string",
"display": true,
"required": false,
"displayName": "focal_point",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "colours",
"type": "string",
"display": true,
"required": false,
"displayName": "colours",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "layout",
"type": "string",
"display": true,
"required": false,
"displayName": "layout",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "text_elements",
"type": "string",
"display": true,
"required": false,
"displayName": "text_elements",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ad_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_9fnWQm3ipnWg3DvP6XD-EnT2e5vZRvVlQyVA0rbNMQ/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "XXXXXX"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "8dzgEvzMeMhDaVGY",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "21dcc80e-ac2f-40ce-9890-6c8ba2ee463c",
"name": "토큰 갱신이 필요한가?",
"type": "n8n-nodes-base.if",
"position": [
-2464,
768
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "73f5e309-6f35-413f-a891-a0bc7185ca60",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.needs_refresh }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "96584b1b-2f7f-46d1-96c7-268c0ea9603b",
"name": "스티키 노트",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3856,
176
],
"parameters": {
"width": 608,
"height": 1024,
"content": "## AI-Powered Facebook Ad Analysis for E-commerce\n\nThis workflow automates the analysis of your Facebook ad performance, acting as an AI-powered media buyer to give you actionable insights. It fetches your ad data, calculates account-wide benchmarks, and then uses a Large Language Model (LLM) to categorize each ad creative's performance, providing clear justifications and recommendations.\n\n### Who is it for?\nThis template is perfect for:\n- E-commerce Store Owners\n- Digital Marketing Agencies\n- Facebook Ads Media Buyers\n\n### What it does\n1. **Secure Token Management**: Automatically retrieves and refreshes your Facebook long-term access token.\n2. **Fetch Ad Data**: Pulls the last 28 days of ad-level performance data from your Facebook Ads account.\n3. **Process & Clean**: Parses the raw data, standardizes metrics (like purchases and ROAS), and filters for sales-focused campaigns.\n4. **Benchmark Calculation**: Aggregates all data to create an overall performance benchmark for your account (e.g., average Cost Per Purchase, average ROAS).\n5. **AI Analysis**: For each ad, it sends its performance data and the account benchmark to an AI. The AI then evaluates the ad, categorizing it as \"HELL YES,\" \"YES,\" \"MAYBE,\" or \"WE WASTED MONEY\" based on a detailed prompt.\n6. **Output to Google Sheets**: Updates a Google Sheet with the raw performance data and the new AI-generated insights, including the performance category, justification, and recommendation.\n\n### How to set up\n1. **Facebook Credentials**:\n - This workflow uses NocoDB to store and refresh the Facebook token. Set up the **Getting Long-Term Token** and **Updating Token** nodes with your NocoDB credentials, or replace this section with your preferred method for storing credentials.\n2. **Google Credentials**:\n - Configure the **Google Sheets** and **Google Gemini** nodes with your respective API credentials.\n3. **Update Your IDs**:\n - In the **Getting Data For the Past 28 Days...** node, replace `act_XXXXXX` in the URL with your Facebook Ad Account ID.\n - In both Google Sheets nodes (**Sending Raw Data...** and **Updating Ad Insights...**), update the *Document ID* with your Google Sheet's ID.\n4. **Run the Workflow**: Once configured, click 'Test workflow' to run the analysis!"
},
"typeVersion": 1
},
{
"id": "5ce47343-8189-4d1d-9038-f0e0bcaf9802",
"name": "장기 토큰 추출",
"type": "n8n-nodes-base.set",
"position": [
-1600,
704
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "fe9ab593-0caa-415b-958c-5afea549b1d2",
"name": "longAccessToken",
"type": "string",
"value": "={{ $json.longTermAccessToken }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5e3dc574-4049-4bee-ae81-8a899b553615",
"name": "스티키 노트5",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
272
],
"parameters": {
"color": 7,
"width": 528,
"height": 624,
"content": "### Step 5: AI-Powered Ad Creative Analysis\n\nA powerful LLM (Google Gemini) acts as a Senior Media Buyer. It compares each ad's performance against the account benchmarks and categorizes it with a justification and recommendation.\n\n**➡️ Action Required:**\n- Ensure your Google Gemini credentials are correctly configured in the **Google Gemini Chat Model** node."
},
"typeVersion": 1
},
{
"id": "2eaabe3c-fec3-4804-8f2b-db39a00cb0c4",
"name": "계정 벤치마크 계산",
"type": "n8n-nodes-base.code",
"position": [
-464,
784
],
"parameters": {
"jsCode": "// --- START OF CODE NODE 3 SCRIPT ---\n// Input: Output from Code Node 1 (array of processed ad records)\n// Mode: Run Once for All Items\n\nconst allProcessedAdRecords = $input.all(); // Array of n8n items from Code Node 1\n\n// Initialize overall totals\nconst overallTotals = {\n total_spend: 0,\n total_impressions: 0,\n total_clicks: 0,\n total_add_to_carts: 0,\n total_checkouts_initiated: 0,\n total_purchases: 0,\n total_purchase_value: 0,\n // We can't really sum 'objective' or 'ad_name' in a meaningful way for an overall summary,\n // so we'll omit them or set them to a generic value.\n // We also won't sum date_start/date_stop, but we can determine the overall period.\n};\n\nlet minDateStart = null;\nlet maxDateStop = null;\n\n// Step 1: Sum up fundamental metrics from all processed ad records\nfor (const n8nItem of allProcessedAdRecords) {\n const adRecord = n8nItem.json; // Each adRecord is an output from Code Node 1\n\n overallTotals.total_spend += parseFloat(adRecord.spend || 0);\n overallTotals.total_impressions += parseInt(adRecord.impressions || 0);\n overallTotals.total_clicks += parseInt(adRecord.clicks || 0);\n overallTotals.total_add_to_carts += parseInt(adRecord.add_to_carts_count || 0);\n overallTotals.total_checkouts_initiated += parseInt(adRecord.checkouts_initiated_count || 0);\n overallTotals.total_purchases += parseInt(adRecord.purchases_count || 0);\n overallTotals.total_purchase_value += parseFloat(adRecord.purchase_value_total || 0);\n\n // Determine overall date range\n if (adRecord.date_start) {\n const currentStartDate = new Date(adRecord.date_start);\n if (!minDateStart || currentStartDate < minDateStart) {\n minDateStart = currentStartDate;\n }\n }\n if (adRecord.date_stop) {\n const currentStopDate = new Date(adRecord.date_stop);\n if (!maxDateStop || currentStopDate > maxDateStop) {\n maxDateStop = currentStopDate;\n }\n }\n}\n\n// Add overall date range to the output\nif (minDateStart) {\n overallTotals.overall_date_start = minDateStart.toISOString().split('T')[0]; // YYYY-MM-DD\n}\nif (maxDateStop) {\n overallTotals.overall_date_stop = maxDateStop.toISOString().split('T')[0]; // YYYY-MM-DD\n}\n\n\n// Step 2: Calculate overall derived KPIs based on the grand totals\nconst overallKPIs = { ...overallTotals }; // Start with the summed totals\n\noverallKPIs.ctr = overallTotals.total_impressions > 0 ? (overallTotals.total_clicks / overallTotals.total_impressions) : 0;\noverallKPIs.cpc = overallTotals.total_clicks > 0 ? (overallTotals.total_spend / overallTotals.total_clicks) : 0;\noverallKPIs.cpm = overallTotals.total_impressions > 0 ? (overallTotals.total_spend / overallTotals.total_impressions * 1000) : 0;\n\noverallKPIs.cost_per_add_to_cart = overallTotals.total_add_to_carts > 0 ? (overallTotals.total_spend / overallTotals.total_add_to_carts) : 0;\noverallKPIs.cost_per_checkout = overallTotals.total_checkouts_initiated > 0 ? (overallTotals.total_spend / overallTotals.total_checkouts_initiated) : 0;\noverallKPIs.cost_per_purchase = overallTotals.total_purchases > 0 ? (overallTotals.total_spend / overallTotals.total_purchases) : 0;\n\noverallKPIs.roas = overallTotals.total_spend > 0 ? (overallTotals.total_purchase_value / overallTotals.total_spend) : 0;\n\nlet conversionRateDecimal = overallTotals.total_clicks > 0 ? (overallTotals.total_purchases / overallTotals.total_clicks) : 0;\noverallKPIs.average_order_value = overallTotals.total_purchases > 0 ? (overallTotals.total_purchase_value / overallTotals.total_purchases) : 0;\n\n// --- Format numbers for the overall summary ---\noverallKPIs.total_spend = parseFloat(overallKPIs.total_spend.toFixed(2));\noverallKPIs.total_purchase_value = parseFloat(overallKPIs.total_purchase_value.toFixed(2));\n\noverallKPIs.ctr = parseFloat(overallKPIs.ctr.toFixed(4));\noverallKPIs.cpc = parseFloat(overallKPIs.cpc.toFixed(2));\noverallKPIs.cpm = parseFloat(overallKPIs.cpm.toFixed(2));\n\noverallKPIs.cost_per_add_to_cart = parseFloat(overallKPIs.cost_per_add_to_cart.toFixed(2));\noverallKPIs.cost_per_checkout = parseFloat(overallKPIs.cost_per_checkout.toFixed(2));\noverallKPIs.cost_per_purchase = parseFloat(overallKPIs.cost_per_purchase.toFixed(2));\n\noverallKPIs.roas = parseFloat(overallKPIs.roas.toFixed(2));\n\noverallKPIs.conversion_rate = (conversionRateDecimal * 100).toFixed(2) + '%'; // As percentage string\noverallKPIs.average_order_value = parseFloat(overallKPIs.average_order_value.toFixed(2));\n\n// This node will output a single item containing the overall summary\nreturn [{ json: overallKPIs }];\n// --- END OF CODE NODE 3 SCRIPT ---"
},
"typeVersion": 2
},
{
"id": "23411043-d16f-40f4-8fcc-3c6653f3595c",
"name": "벤치마크 데이터 문자열화",
"type": "n8n-nodes-base.code",
"position": [
-48,
784
],
"parameters": {
"jsCode": "// --- START OF CODE NODE 4 SCRIPT (Stringify Output) ---\n// Mode: Run Once for All Items\n// Input: The array of aggregated ad creative performance objects\n\nconst allAggregatedItems = $input.all(); // This is an array of n8n items\n\n// Extract the .json part from each n8n item to get the actual data objects\nconst dataToConvert = allAggregatedItems.map(item => item.json);\n\n// Stringify the array of data objects\n// The 'null, 2' arguments pretty-print the JSON string with an indent of 2 spaces,\n// which can be helpful for readability if you're inspecting it, but not strictly necessary for an LLM.\n// If the LLM prefers a more compact JSON, you can omit 'null, 2'.\nconst jsonString = JSON.stringify(dataToConvert, null, 2);\n\n// Output a single item containing this string\nreturn [{ json: { benchmarkData: jsonString } }];\n// --- END OF CODE NODE 4 SCRIPT ---"
},
"typeVersion": 2
},
{
"id": "cf388ebd-dff0-4658-b13a-59619c8cf701",
"name": "광고 소재별 지표 집계",
"type": "n8n-nodes-base.code",
"position": [
-496,
448
],
"parameters": {
"jsCode": "// --- START OF CODE NODE 2 SCRIPT (Aggregate by Ad Creative & KPIs) ---\n// Mode: Run Once for All Items\n// Input: Output from Code Node 1 (which now includes ad_id)\n\nconst allInputItems = $input.all();\nconst aggregatedByAdName = {};\n\n// Step 1: Aggregate metrics by ad_name\nfor (const n8nItem of allInputItems) {\n const itemData = n8nItem.json; // Data from Code Node 1, includes ad_id\n const key = itemData.ad_name;\n\n if (!aggregatedByAdName[key]) {\n aggregatedByAdName[key] = {\n ad_name: itemData.ad_name,\n ad_id: itemData.ad_id, // <<< STORE THE AD_ID OF THE FIRST ENCOUNTERED ITEM\n objective: itemData.objective,\n // Initialize all sums\n total_spend: 0,\n total_impressions: 0,\n total_clicks: 0,\n total_add_to_carts: 0,\n total_checkouts_initiated: 0,\n total_purchases: 0,\n total_purchase_value: 0,\n };\n }\n\n // If the first itemData for an ad_name somehow missed an ad_id (unlikely if Code Node 1 is correct),\n // but a subsequent one has it, this ensures it gets populated.\n if (itemData.ad_id && !aggregatedByAdName[key].ad_id) {\n aggregatedByAdName[key].ad_id = itemData.ad_id;\n }\n // Same for objective, if needed\n if (itemData.objective && !aggregatedByAdName[key].objective) {\n aggregatedByAdName[key].objective = itemData.objective;\n }\n\n // Sum up the metrics\n aggregatedByAdName[key].total_spend += parseFloat(itemData.spend || 0);\n aggregatedByAdName[key].total_impressions += parseInt(itemData.impressions || 0);\n aggregatedByAdName[key].total_clicks += parseInt(itemData.clicks || 0);\n aggregatedByAdName[key].total_add_to_carts += parseInt(itemData.add_to_carts_count || 0);\n aggregatedByAdName[key].total_checkouts_initiated += parseInt(itemData.checkouts_initiated_count || 0);\n aggregatedByAdName[key].total_purchases += parseInt(itemData.purchases_count || 0);\n aggregatedByAdName[key].total_purchase_value += parseFloat(itemData.purchase_value_total || 0);\n}\n\n// Step 2: Calculate derived metrics and prepare final output\nconst finalOutputArray = [];\nfor (const key in aggregatedByAdName) {\n const adCreativeData = aggregatedByAdName[key];\n const outputItem = { ...adCreativeData }; // Includes ad_name, ad_id, objective, and all total_... fields\n\n // Calculate derived metrics, handling division by zero\n outputItem.ctr = adCreativeData.total_impressions > 0 ? (adCreativeData.total_clicks / adCreativeData.total_impressions) : 0;\n outputItem.cpc = adCreativeData.total_clicks > 0 ? (adCreativeData.total_spend / adCreativeData.total_clicks) : 0;\n outputItem.cpm = adCreativeData.total_impressions > 0 ? (adCreativeData.total_spend / adCreativeData.total_impressions * 1000) : 0;\n \n outputItem.cost_per_add_to_cart = adCreativeData.total_add_to_carts > 0 ? (adCreativeData.total_spend / adCreativeData.total_add_to_carts) : 0;\n outputItem.cost_per_checkout = adCreativeData.total_checkouts_initiated > 0 ? (adCreativeData.total_spend / adCreativeData.total_checkouts_initiated) : 0;\n outputItem.cost_per_purchase = adCreativeData.total_purchases > 0 ? (adCreativeData.total_spend / adCreativeData.total_purchases) : 0;\n \n outputItem.roas = adCreativeData.total_spend > 0 ? (adCreativeData.total_purchase_value / adCreativeData.total_spend) : 0;\n\n let conversionRateDecimal = adCreativeData.total_clicks > 0 ? (adCreativeData.total_purchases / adCreativeData.total_clicks) : 0;\n outputItem.average_order_value = adCreativeData.total_purchases > 0 ? (adCreativeData.total_purchase_value / adCreativeData.total_purchases) : 0;\n\n // Format numbers\n outputItem.total_spend = parseFloat(outputItem.total_spend.toFixed(2));\n outputItem.total_purchase_value = parseFloat(outputItem.total_purchase_value.toFixed(2));\n \n outputItem.ctr = parseFloat(outputItem.ctr.toFixed(4));\n outputItem.cpc = parseFloat(outputItem.cpc.toFixed(2));\n outputItem.cpm = parseFloat(outputItem.cpm.toFixed(2));\n \n outputItem.cost_per_add_to_cart = parseFloat(outputItem.cost_per_add_to_cart.toFixed(2));\n outputItem.cost_per_checkout = parseFloat(outputItem.cost_per_checkout.toFixed(2));\n outputItem.cost_per_purchase = parseFloat(outputItem.cost_per_purchase.toFixed(2));\n \n outputItem.roas = parseFloat(outputItem.roas.toFixed(2));\n\n outputItem.conversion_rate = (conversionRateDecimal * 100).toFixed(2) + '%';\n outputItem.average_order_value = parseFloat(outputItem.average_order_value.toFixed(2));\n\n finalOutputArray.push({ json: outputItem });\n}\n\nreturn finalOutputArray;\n// --- END OF CODE NODE 2 SCRIPT ---"
},
"typeVersion": 2
},
{
"id": "3b80f5a8-4aa8-4a65-882b-ebc391da066c",
"name": "LLM용 광고 및 벤치마크 데이터 결합",
"type": "n8n-nodes-base.aggregate",
"position": [
400,
512
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "dbc2be05-df91-463b-bd18-d935809393a1",
"name": "액세스 토큰 추출",
"type": "n8n-nodes-base.set",
"position": [
-1360,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a6e2f5e6-eebb-43ea-9c51-9016c673d4da",
"name": "accessToken",
"type": "string",
"value": "={{ $json.longAccessToken }}"
}
]
}
},
"typeVersion": 3.4
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "13e4e553-de65-4188-9e59-6517c3cdcb5c",
"connections": {
"733a2556-a9d9-4892-9acb-6fcdb32a326e": {
"main": [
[
{
"node": "f44bb155-47a1-422d-b857-6007bbcb9844",
"type": "main",
"index": 0
}
]
]
},
"c53eca75-6433-46fb-95ac-3b85a1b6420a": {
"main": [
[
{
"node": "5ce47343-8189-4d1d-9038-f0e0bcaf9802",
"type": "main",
"index": 0
}
]
]
},
"89a2936e-7719-4cd8-8638-7db0d13e1a3f": {
"main": [
[
{
"node": "6256c78d-67fb-4bc1-afb9-07b6191e254d",
"type": "main",
"index": 0
}
]
]
},
"dbc2be05-df91-463b-bd18-d935809393a1": {
"main": [
[
{
"node": "b2f42335-8b1e-4d00-a9a8-f7f631f1927d",
"type": "main",
"index": 0
}
]
]
},
"7b2463ab-0eac-4bd5-8185-744ea231ef1d": {
"main": [
[
{
"node": "21dcc80e-ac2f-40ce-9890-6c8ba2ee463c",
"type": "main",
"index": 0
}
]
]
},
"83a6b0a8-8422-4705-9576-deca22f7f092": {
"main": [
[
{
"node": "aa9a05cc-532f-4f6f-844b-5511178b1722",
"type": "main",
"index": 0
},
{
"node": "89a2936e-7719-4cd8-8638-7db0d13e1a3f",
"type": "main",
"index": 0
}
]
]
},
"fe5643c3-3012-4573-84b7-d11c93261bee": {
"main": [
[
{
"node": "dbc2be05-df91-463b-bd18-d935809393a1",
"type": "main",
"index": 0
}
]
]
},
"48d96bc5-b537-4fb0-9f09-e8c75f57a1c2": {
"ai_languageModel": [
[
{
"node": "2dfd3d0c-0988-42df-8dfc-c545188d4ea2",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"23411043-d16f-40f4-8fcc-3c6653f3595c": {
"main": [
[
{
"node": "6256c78d-67fb-4bc1-afb9-07b6191e254d",
"type": "main",
"index": 1
}
]
]
},
"e5ce198d-0740-456f-9160-6f7b253cdbe0": {
"ai_outputParser": [
[
{
"node": "2dfd3d0c-0988-42df-8dfc-c545188d4ea2",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"5ce47343-8189-4d1d-9038-f0e0bcaf9802": {
"main": [
[
{
"node": "dbc2be05-df91-463b-bd18-d935809393a1",
"type": "main",
"index": 0
}
]
]
},
"2eaabe3c-fec3-4804-8f2b-db39a00cb0c4": {
"main": [
[
{
"node": "23411043-d16f-40f4-8fcc-3c6653f3595c",
"type": "main",
"index": 0
}
]
]
},
"21dcc80e-ac2f-40ce-9890-6c8ba2ee463c": {
"main": [
[
{
"node": "21dcc80e-ac2f-40ce-9890-6c8ba2ee463c",
"type": "main",
"index": 0
}
]
]
},
"ce1cff8f-e076-4ec5-9a86-58db01605894": {
"main": [
[
{
"node": "2fdd3e2f-92fa-4034-841a-500dad9f91bc",
"type": "main",
"index": 0
}
]
]
},
"72f99e3f-1c6c-4186-b0d8-1c10d5c32fa7": {
"main": [
[
{
"node": "c53eca75-6433-46fb-95ac-3b85a1b6420a",
"type": "main",
"index": 0
}
]
]
},
"2dfd3d0c-0988-42df-8dfc-c545188d4ea2": {
"main": [
[
{
"node": "733a2556-a9d9-4892-9acb-6fcdb32a326e",
"type": "main",
"index": 0
}
]
]
},
"cf388ebd-dff0-4658-b13a-59619c8cf701": {
"main": [
[
{
"node": "83a6b0a8-8422-4705-9576-deca22f7f092",
"type": "main",
"index": 0
}
]
]
},
"5f27af56-537f-42f3-9c11-52189918c631": {
"main": [
[
{
"node": "72f99e3f-1c6c-4186-b0d8-1c10d5c32fa7",
"type": "main",
"index": 0
}
]
]
},
"ce48c496-4d57-41c3-992e-046c1da7d6f6": {
"main": [
[
{
"node": "7b2463ab-0eac-4bd5-8185-744ea231ef1d",
"type": "main",
"index": 0
}
]
]
},
"2fdd3e2f-92fa-4034-841a-500dad9f91bc": {
"main": [
[
{
"node": "cf388ebd-dff0-4658-b13a-59619c8cf701",
"type": "main",
"index": 0
},
{
"node": "2eaabe3c-fec3-4804-8f2b-db39a00cb0c4",
"type": "main",
"index": 0
}
]
]
},
"3b80f5a8-4aa8-4a65-882b-ebc391da066c": {
"main": [
[
{
"node": "2dfd3d0c-0988-42df-8dfc-c545188d4ea2",
"type": "main",
"index": 0
}
]
]
},
"6256c78d-67fb-4bc1-afb9-07b6191e254d": {
"main": [
[
{
"node": "3b80f5a8-4aa8-4a65-882b-ebc391da066c",
"type": "main",
"index": 0
}
]
]
},
"b2f42335-8b1e-4d00-a9a8-f7f631f1927d": {
"main": [
[
{
"node": "ce1cff8f-e076-4ec5-9a86-58db01605894",
"type": "main",
"index": 0
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 시장 조사, AI 요약
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
시각화 참조 라이브러리에서 n8n 노드를 탐색
可视化 참조 라이브러리에서 n8n 노드를 탐색
If
Ftp
Set
+
If
Ftp
Set
113 노드I versus AI
기타
주제 사냥꾼 템플릿
사용자 Gemini를 통해 Reddit, YouTube, X에서 내용 전략 보고서 생성
If
Set
Code
+
If
Set
Code
34 노드Sheryl
시장 조사
연락처 정보 풍부화
Apollo, LinkedIn 및 GPT-4o 기반의 전면적인 연락처 정보 풍부화, HubSpot에 적합
If
Set
Code
+
If
Set
Code
24 노드Interlock GTM
리드 생성
콘텐츠 집계
Gemini AI로 웹사이트 글에서 소셜 미디어 게시물 자동 생성 및 LinkedIn 및 X/Twitter에 게시
If
Set
Xml
+
If
Set
Xml
34 노드Vadim
콘텐츠 제작
커뮤니티 문제 모니터링 및 OpenRouter AI, Reddit, 포럼 크롤링
OpenRouter AI, Reddit, 포럼을 사용하여 커뮤니티 문제를 모니터링합니다.
Set
Code
Html
+
Set
Code
Html
29 노드Julian Kaiser
시장 조사
매일 WhatsApp 그룹 지능형 분석: GPT-4.1 분석 및 음성 메시지 변환
매일 WhatsApp 그룹 지능 분석: GPT-4.1 분석 및 음성 메시지 트랜스크립션
If
Set
Code
+
If
Set
Code
52 노드Daniel Lianes
기타
워크플로우 정보
난이도
고급
노드 수34
카테고리2
노드 유형16
저자
JJ Tham
@jj-thamFounder of Osinity. I build AI-powered n8n automations that save businesses 10+ hours a week and grow their revenue. We guarantee results with a 30-day risk-free trial—you only pay when we hit your targets.
외부 링크
n8n.io에서 보기 →
이 워크플로우 공유