This workflow corresponds to n8n.io template #15702 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request 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": "rGTB6Zv4mwrrNVA1",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Octagon Stock Quote Skill to Google Sheets",
"tags": [],
"nodes": [
{
"id": "daa754b6-a730-43ae-82ff-78a810b97bf6",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
1168
],
"parameters": {
"width": 480,
"height": 608,
"content": "## Octagon Stock Quote Skill to Google Sheets\n\n### How it works\n\n1. Manually triggers the workflow to start.\n2. Fetches stock quote skill details from GitHub.\n3. Reads tickers from a Google Sheet and filters them.\n4. Processes each ticker with an AI prompt and updates the Google Sheet.\n5. Throttles task execution to manage processing load.\n\n### Setup steps\n\n- [ ] Configure GitHub API access for fetching the skill.\n- [ ] Set up Google Sheets API credentials to read and write data.\n\n### Customization\n\nCustomize the filters and AI prompts based on specific stock analysis needs."
},
"typeVersion": 1
},
{
"id": "cc1eb228-bfcf-473c-b9dc-e8586a08bd63",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2160,
1280
],
"parameters": {
"color": 7,
"width": 240,
"height": 304,
"content": "## Manual start trigger\n\nInitiates the workflow manually."
},
"typeVersion": 1
},
{
"id": "ccffc61d-2da4-4aad-b3ee-2e3aa029359b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2432,
1280
],
"parameters": {
"color": 7,
"width": 416,
"height": 304,
"content": "## Setup task instructions\n\nSets the task and fetches stock quote skill details from GitHub."
},
"typeVersion": 1
},
{
"id": "019758b6-ac12-4eb1-ad8f-2727d1a668c7",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2880,
1280
],
"parameters": {
"color": 7,
"width": 416,
"height": 304,
"content": "## Read and filter tickers\n\nReads tickers from Google Sheets and filters valid ones for further processing."
},
"typeVersion": 1
},
{
"id": "0a675422-3eed-41c8-9c38-590cc647def0",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3328,
1200
],
"parameters": {
"color": 7,
"width": 768,
"height": 384,
"content": "## Process tickers with AI\n\nLoops through filtered tickers, processes them using AI, and retrieves the stock information."
},
"typeVersion": 1
},
{
"id": "1bbcc303-a317-47d5-8007-ffdfd5081e8d",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
4304,
1168
],
"parameters": {
"color": 7,
"width": 416,
"height": 400,
"content": "## Update results and throttle\n\nUpdates the Google Sheet with stock information and throttles execution to control workflow speed."
},
"typeVersion": 1
},
{
"id": "399a7264-f4d4-47aa-8381-43ab687640f1",
"name": "Manual Workflow Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
2208,
1408
],
"parameters": {},
"typeVersion": 1
},
{
"id": "437c2711-9059-4e2f-9193-b06537bad029",
"name": "Set Task and Skills URL",
"type": "n8n-nodes-base.set",
"position": [
2480,
1408
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "27c3c6bc-7784-4b87-9af4-64aa5912b26f",
"name": "task",
"type": "string",
"value": "Get real-time stock quotes for the tickers in the Google Sheet. For each ticker, build the best Octagon Agent prompt using the fetched stock-quote skill context. Return compact quote fields suitable for updating PRICE and DATE."
},
{
"id": "6464cc3d-eaeb-493c-8b64-a6278bb43de5",
"name": "skills_url",
"type": "string",
"value": "https://raw.githubusercontent.com/OctagonAI/skills/main/skills/stock-quote/SKILL.md"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2d208d52-9693-4f89-a77c-c63a9d50da17",
"name": "Filter Valid Stock Tickers",
"type": "n8n-nodes-base.filter",
"position": [
3152,
1408
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7e47b022-8b22-4cac-9c3e-72287615ef97",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ ($json.Ticker || $json.TICKER || '').trim() }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f02a35eb-d53f-4533-99aa-8cd5d763dde2",
"name": "Loop Over Tickers",
"type": "n8n-nodes-base.splitInBatches",
"position": [
3376,
1408
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "be7835f9-b8b8-4ac6-802c-5b59819344eb",
"name": "Create Octagon API Prompt",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3600,
1328
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"responses": {
"values": [
{
"content": "={{ \n(() => {\n const inputs = $('Set Task and Skills URL').first().json;\n const skillNode = $('Fetch Skill from GitHub URL').first().json;\n\n const skillContent =\n typeof skillNode === 'string'\n ? skillNode\n : skillNode.data || skillNode.body || skillNode.response || skillNode.text || JSON.stringify(skillNode);\n\n return `You are a prompt builder for Octagon Agent.\n\nObjective:\nGiven the workflow task, the current Google Sheets row, and the fetched SKILL.md context, produce the exact prompt that should be sent to Octagon Agent.\n\nRules:\n- Output only the final prompt text.\n- Do not include markdown fences.\n- Do not explain your reasoning.\n- Use the stock-quote skill's intended workflow.\n- The prompt must ask Octagon Agent for a real-time stock quote.\n- The prompt must be specific to the ticker in the current row.\n- The prompt should request a compact JSON object that includes at least: ticker, current_price, quote_time, and source/status.\n- If the workflow task asks for extra fields supported by the skill, include them.\n- If the ticker is missing, output: ERROR: Missing ticker.\n\nWorkflow task:\n${inputs.task}\n\nCurrent ticker:\n${$json.Ticker || $json.TICKER || ''}\n\nCurrent Google Sheets row:\n${JSON.stringify($json, null, 2)}\n\nFetched stock-quote SKILL.md from:\n${inputs.skills_url}\n\nFetched stock-quote SKILL.md content:\n${skillContent}`;\n})()\n}}"
}
]
},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "273f15f4-4add-4fb3-89ad-b2468d4f0535",
"name": "Execute Octagon Agent",
"type": "n8n-nodes-octagon.octagonAgents",
"position": [
3952,
1328
],
"parameters": {
"query": "={{ \n(() => {\n const item = $json;\n\n if (typeof item === 'string') return item;\n\n if (item.output_text) return item.output_text;\n if (item.text) return item.text;\n\n if (item.message?.content) {\n if (typeof item.message.content === 'string') return item.message.content;\n if (Array.isArray(item.message.content)) {\n return item.message.content.map(c => c.text || c.content || '').join('\\n').trim();\n }\n }\n\n if (Array.isArray(item.content)) {\n return item.content.map(c => c.text || c.content || '').join('\\n').trim();\n }\n\n if (item.query) {\n try {\n const q = typeof item.query === 'string' ? JSON.parse(item.query) : item.query;\n const text = q?.output?.[0]?.content?.[0]?.text;\n if (text) return text;\n } catch (e) {\n return item.query;\n }\n }\n\n return JSON.stringify(item);\n})()\n}}",
"additionalFields": {}
},
"credentials": {
"octagonApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c41eb433-dc8b-4650-a5b4-632d8962b1d7",
"name": "Update Tickers in Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
4352,
1328
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json.Date }}",
"Change": "={{ $json.Change }}",
"Ticker": "={{ $json.Ticker }}",
"Volume": "={{ $json.Volume }}",
"Exchange": "={{ $json.Exchange }}",
"Day Range": "={{ $json['Day Range'] }}",
"Market Cap": "={{ $json['Market Cap'] }}",
"52-Week Range": "={{ $json['52-Week Range'] }}",
"Current Price": "={{ $json['Current Price'] }}",
"50-Day Average": "={{ $json['50-Day Average'] }}",
"Previous Close": "={{ $json['Previous Close'] }}",
"200-Day Average": "={{ $json['200-Day Average'] }}",
"Octagon Response": "={{ $json['Octagon Response'] }}",
"Octagon Source/Status": "={{ $json['Octagon Source/Status'] }}"
},
"schema": [
{
"id": "Ticker",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Ticker",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Current Price",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Current Price",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Change",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Change",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Volume",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Volume",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Day Range",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Day Range",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "52-Week Range",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "52-Week Range",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Market Cap",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Market Cap",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Exchange",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Exchange",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Previous Close",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Previous Close",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Octagon Response",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Octagon Response",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "50-Day Average",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "50-Day Average",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "200-Day Average",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "200-Day Average",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Octagon Source/Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Octagon Source/Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Ticker"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit?usp=drivesdk",
"cachedResultName": "TICKERS"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "0fb9e984-3765-4f42-9000-eb446ea0cff4",
"name": "Wait 1 Second",
"type": "n8n-nodes-base.wait",
"position": [
4576,
1408
],
"parameters": {
"amount": 1
},
"typeVersion": 1.1
},
{
"id": "6c9b80a3-1556-4419-9f9c-11f65f34e8bd",
"name": "Read Tickers in Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
2928,
1408
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit?usp=drivesdk",
"cachedResultName": "TICKERS"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "5c9af8b2-93e7-4b2d-b5ce-215bad93db30",
"name": "Fetch Skill from GitHub URL",
"type": "n8n-nodes-base.httpRequest",
"position": [
2704,
1408
],
"parameters": {
"url": "={{ $json.skills_url }}",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "f25a58c3-9fbd-4bd2-a9e0-96fb37f59d6f",
"name": "Parse Octagon Quote Response",
"type": "n8n-nodes-base.code",
"position": [
4176,
1328
],
"parameters": {
"jsCode": "// Parse Octagon Agent response and merge all quote fields back with the current ticker row.\n// Works with Octagon outputs returned as JSON, markdown tables, plain text, or nested OpenAI-style payloads.\n\nfunction getCurrentTickerRow() {\n try {\n return $('Loop Over Tickers').item.json || {};\n } catch (e) {}\n\n try {\n return $('Loop Over Tickers').first().json || {};\n } catch (e) {}\n\n return {};\n}\n\nfunction stringifySafe(value) {\n if (typeof value === 'string') return value;\n if (value == null) return '';\n try {\n return JSON.stringify(value, null, 2);\n } catch (e) {\n return String(value);\n }\n}\n\nfunction unwrapText(value) {\n if (value == null) return '';\n if (typeof value === 'string') return value;\n\n // Common Octagon / agent fields\n if (typeof value.analysis === 'string') return value.analysis;\n if (typeof value.output_text === 'string') return value.output_text;\n if (typeof value.text === 'string') return value.text;\n if (typeof value.response === 'string') return value.response;\n if (typeof value.result === 'string') return value.result;\n if (typeof value.answer === 'string') return value.answer;\n\n // OpenAI-style message/content arrays\n if (value.message?.content) {\n if (typeof value.message.content === 'string') return value.message.content;\n if (Array.isArray(value.message.content)) {\n return value.message.content\n .map(c => c?.text || c?.content || c?.value || '')\n .join('\\n')\n .trim();\n }\n }\n\n if (Array.isArray(value.content)) {\n return value.content\n .map(c => c?.text || c?.content || c?.value || '')\n .join('\\n')\n .trim();\n }\n\n // Some Octagon node responses place the generated prompt/result inside query as JSON string\n if (value.query) {\n try {\n const q = typeof value.query === 'string' ? JSON.parse(value.query) : value.query;\n const text = q?.output?.[0]?.content?.[0]?.text;\n if (text) return text;\n } catch (e) {\n if (typeof value.query === 'string') return value.query;\n }\n }\n\n return stringifySafe(value);\n}\n\nfunction stripCodeFences(text) {\n return String(text || '')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```javascript\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/```$/i, '')\n .trim();\n}\n\nfunction parseJsonObjectFromText(text) {\n const cleaned = stripCodeFences(text);\n if (!cleaned) return {};\n\n // Try the whole text first\n try {\n const parsed = JSON.parse(cleaned);\n if (parsed && typeof parsed === 'object') return parsed;\n } catch (e) {}\n\n // Try each balanced-ish object candidate from first { to each later }\n const firstBrace = cleaned.indexOf('{');\n const lastBrace = cleaned.lastIndexOf('}');\n if (firstBrace >= 0 && lastBrace > firstBrace) {\n const candidate = cleaned.slice(firstBrace, lastBrace + 1);\n try {\n const parsed = JSON.parse(candidate);\n if (parsed && typeof parsed === 'object') return parsed;\n } catch (e) {}\n }\n\n return {};\n}\n\nfunction flattenObject(obj, prefix = '', out = {}) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return out;\n\n for (const [key, value] of Object.entries(obj)) {\n const cleanKey = String(key).trim();\n const path = prefix ? `${prefix}.${cleanKey}` : cleanKey;\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n flattenObject(value, path, out);\n } else {\n out[path] = value;\n out[cleanKey] = value; // keep simple alias too\n }\n }\n\n return out;\n}\n\nfunction normalizeKey(key) {\n return String(key || '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '');\n}\n\nfunction getFirst(obj, keys, fallback = '') {\n if (!obj || typeof obj !== 'object') return fallback;\n\n const flat = flattenObject(obj);\n const normalized = {};\n\n for (const [key, value] of Object.entries(flat)) {\n normalized[normalizeKey(key)] = value;\n }\n\n for (const key of keys) {\n const direct = flat[key];\n if (direct !== undefined && direct !== null && direct !== '') return direct;\n\n const norm = normalized[normalizeKey(key)];\n if (norm !== undefined && norm !== null && norm !== '') return norm;\n }\n\n return fallback;\n}\n\nfunction extractMetric(text, metric) {\n const escapedMetric = metric.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Markdown table row: | Metric | Value |\n const tableRegex = new RegExp(`\\\\|\\\\s*${escapedMetric}\\\\s*\\\\|\\\\s*([^|]+)\\\\|`, 'i');\n const tableMatch = String(text || '').match(tableRegex);\n if (tableMatch) return tableMatch[1].trim();\n\n // Label/value forms: Metric: value OR Metric - value\n const labelRegex = new RegExp(`${escapedMetric}\\\\s*[:\\\\-\u2013\u2014]\\\\s*([^\\\\n|]+)`, 'i');\n const labelMatch = String(text || '').match(labelRegex);\n if (labelMatch) return labelMatch[1].trim();\n\n return '';\n}\n\nfunction firstMetric(text, labels) {\n for (const label of labels) {\n const value = extractMetric(text, label);\n if (value) return value;\n }\n return '';\n}\n\nconst original = getCurrentTickerRow();\nconst octagonRaw = $input.first().json;\nconst raw = stringifySafe(octagonRaw);\nconst analysis = unwrapText(octagonRaw) || raw;\nconst parsed = parseJsonObjectFromText(analysis);\n\nconst ticker =\n original.Ticker ||\n original.TICKER ||\n getFirst(parsed, ['ticker', 'symbol', 'quote.ticker', 'quote.symbol']) ||\n firstMetric(analysis, ['Ticker', 'Symbol']);\n\nconst currentPrice =\n getFirst(parsed, [\n 'current_price', 'currentPrice', 'price', 'last_price', 'lastPrice', 'last',\n 'Last Price', 'Current Price', 'quote.current_price', 'quote.price', 'quote.last_price'\n ]) ||\n firstMetric(analysis, ['Last Price', 'Current Price', 'Price', 'Current']);\n\nconst change =\n getFirst(parsed, ['change', 'price_change', 'priceChange', 'regularMarketChange', 'quote.change']) ||\n firstMetric(analysis, ['Change', 'Price Change']);\n\nconst volume =\n getFirst(parsed, ['volume', 'regularMarketVolume', 'quote.volume']) ||\n firstMetric(analysis, ['Volume']);\n\nconst dayRange =\n getFirst(parsed, ['day_range', 'dayRange', 'regularMarketDayRange', 'quote.day_range']) ||\n firstMetric(analysis, ['Day Range', 'Daily Range']);\n\nconst week52Range =\n getFirst(parsed, [\n '52_week_range', '52WeekRange', 'fifty_two_week_range', 'fiftyTwoWeekRange',\n 'week_52_range', 'quote.52_week_range'\n ]) ||\n firstMetric(analysis, ['52W Range', '52-Week Range', '52 Week Range', '52-week range']);\n\nconst marketCap =\n getFirst(parsed, ['market_cap', 'marketCap', 'regularMarketMarketCap', 'quote.market_cap']) ||\n firstMetric(analysis, ['Market Cap', 'Market Capitalization']);\n\nconst exchange =\n getFirst(parsed, ['exchange', 'exchangeName', 'fullExchangeName', 'quote.exchange']) ||\n firstMetric(analysis, ['Exchange']);\n\nconst previousClose =\n getFirst(parsed, ['previous_close', 'previousClose', 'regularMarketPreviousClose', 'quote.previous_close']) ||\n firstMetric(analysis, ['Previous Close', 'Prev Close']);\n\nconst ma50 =\n getFirst(parsed, [\n '50_day_average', 'fifty_day_average', 'fiftyDayAverage', '50_day_ma',\n 'fifty_day_ma', '50DayAverage', 'quote.50_day_average'\n ]) ||\n firstMetric(analysis, ['50-Day Average', '50-Day MA', '50-Day Moving Average', '50 Day Average']);\n\nconst ma200 =\n getFirst(parsed, [\n '200_day_average', 'two_hundred_day_average', 'twoHundredDayAverage', '200_day_ma',\n '200DayAverage', 'quote.200_day_average'\n ]) ||\n firstMetric(analysis, ['200-Day Average', '200-Day MA', '200-Day Moving Average', '200 Day Average']);\n\nconst date =\n getFirst(parsed, ['quote_time', 'quoteTime', 'as_of', 'asOf', 'timestamp', 'date', 'time', 'metadata.timestamp']) ||\n firstMetric(analysis, ['As Of (UTC)', 'As Of', 'Quote Time', 'Timestamp', 'Date']) ||\n octagonRaw.metadata?.timestamp ||\n new Date().toISOString();\n\nconst sourceStatus =\n getFirst(parsed, ['source_status', 'sourceStatus', 'source', 'status', 'provider']) ||\n firstMetric(analysis, ['Source', 'Status', 'Source/Status']);\n\nreturn [{\n json: {\n row_number: original.row_number,\n Ticker: ticker,\n 'Current Price': currentPrice,\n Change: change,\n Volume: volume,\n 'Day Range': dayRange,\n '52-Week Range': week52Range,\n 'Market Cap': marketCap,\n Exchange: exchange,\n 'Previous Close': previousClose,\n '50-Day Average': ma50,\n '200-Day Average': ma200,\n Date: date,\n 'Octagon Response': analysis,\n 'Octagon Source/Status': sourceStatus,\n _parsedQuoteJson: parsed\n }\n}];"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "fe0e39e0-e779-4f8d-b796-182c33411112",
"connections": {
"Wait 1 Second": {
"main": [
[
{
"node": "Loop Over Tickers",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Tickers": {
"main": [
[],
[
{
"node": "Create Octagon API Prompt",
"type": "main",
"index": 0
}
]
]
},
"Execute Octagon Agent": {
"main": [
[
{
"node": "Parse Octagon Quote Response",
"type": "main",
"index": 0
}
]
]
},
"Read Tickers in Sheets": {
"main": [
[
{
"node": "Filter Valid Stock Tickers",
"type": "main",
"index": 0
}
]
]
},
"Manual Workflow Trigger": {
"main": [
[
{
"node": "Set Task and Skills URL",
"type": "main",
"index": 0
}
]
]
},
"Set Task and Skills URL": {
"main": [
[
{
"node": "Fetch Skill from GitHub URL",
"type": "main",
"index": 0
}
]
]
},
"Update Tickers in Sheets": {
"main": [
[
{
"node": "Wait 1 Second",
"type": "main",
"index": 0
}
]
]
},
"Create Octagon API Prompt": {
"main": [
[
{
"node": "Execute Octagon Agent",
"type": "main",
"index": 0
}
]
]
},
"Filter Valid Stock Tickers": {
"main": [
[
{
"node": "Loop Over Tickers",
"type": "main",
"index": 0
}
]
]
},
"Fetch Skill from GitHub URL": {
"main": [
[
{
"node": "Read Tickers in Sheets",
"type": "main",
"index": 0
}
]
]
},
"Parse Octagon Quote Response": {
"main": [
[
{
"node": "Update Tickers in Sheets",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApioctagonApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate real-time stock quote enrichment in Google Sheets using n8n, OpenAI, and Octagon Agent. This workflow reads ticker symbols from a spreadsheet, fetches the live stock-quote Skill from GitHub, uses an OpenAI prompt-builder step to generate the best Octagon Agent query,…
Source: https://n8n.io/workflows/15702/ — 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.
🎯 Create viral TikToks, Shorts, Reels, podcasts, and ASMR videos in minutes — all on autopilot.
Generate AI viral videos with NanoBanana & VEO3, shared on socials via Blotato 2. Uses @blotato/n8n-nodes-blotato, googleSheets, lmChatOpenAi, toolThink. Event-driven trigger; 94 nodes.
Inbox Guardian. Uses gmailTrigger, lmChatOpenAi, agent, textClassifier. Event-driven trigger; 66 nodes.
This template is designed for marketers, content creators, and e-commerce brands who want to automate the creation of professional ad videos at scale. It’s ideal for teams looking to generate consiste
💥 Automate video ads with NanoBanana, Seedream 4, ChatGPT Image and Veo 3 - VIDE. Uses telegram, telegramTrigger, googleDrive, agent. Event-driven trigger; 63 nodes.