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.
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
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.
This n8n workflow is designed for Facebook Page administrators, social media managers, and community moderators who want to automate comment management on their Facebook Pages. It's perfect for busine