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 →
{
"name": "02 - Claude API Single Record Analysis v2 RESILIENT ROUTER TEST DYNAMIC SHEET",
"nodes": [
{
"id": "rr000000-0000-0000-0000-000000000001",
"name": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 Resilient Router",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-240,
-320
],
"parameters": {
"content": "RESILIENT ROUTER TEST \u2014 \u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 C+E: JSON Repair Formatter + Multi-Tab Router.\n\n\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430:\n1. Primary Claude Analysis (baseline raw JSON)\n2. Parse Primary JSON \u2192 \u0435\u0441\u043b\u0438 \u043d\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u2192 Repair Formatter (\u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u044b\u0437\u043e\u0432 Claude)\n3. Router \u2192 6 \u0432\u043a\u043b\u0430\u0434\u043e\u043a Google Sheets: results / review_queue / monitor_queue / content_queue / skipped_log / technical_errors\n\n\u0422\u0440\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430:\n\u2022 parsed_success \u2014 \u043f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439 \u0438\u043b\u0438 \u043e\u0442\u0440\u0435\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 JSON \u0432\u0430\u043b\u0438\u0434\u0435\u043d\n\u2022 business_skip \u2014 Claude \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u0437\u0430\u043f\u0438\u0441\u044c \u043d\u0435\u0440\u0435\u043b\u0435\u0432\u0430\u043d\u0442\u043d\u0430\n\u2022 technical_error \u2014 JSON \u043d\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u043d\u0438 primary, \u043d\u0438 repair\n\n\u0422\u0435\u0441\u0442\u044b A-E:\n\u2022 A \u2014 \u0433\u043e\u0440\u044f\u0447\u0438\u0439 \u043b\u0438\u0434 \u2192 results\n\u2022 B \u2014 \u0441\u043b\u0430\u0431\u044b\u0439 \u043b\u0438\u0434 \u2192 review_queue\n\u2022 C \u2014 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442 \u2192 monitor_queue\n\u2022 D \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 Markdown \u2192 repair \u2192 results\n\u2022 E \u2014 \u043d\u0435\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u2192 technical_errors\n\n\u041c\u0435\u043d\u044f\u0439 test_id \u0432 Set Test Selector (A/B/C/D/E). active=false \u2014 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c.",
"height": 280,
"width": 1100,
"color": 5
}
},
{
"id": "rr000000-0000-0000-0000-000000000021",
"name": "\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
2900,
-320
],
"parameters": {
"content": "\u041f\u041e\u0420\u042f\u0414\u041e\u041a \u0422\u0415\u0421\u0422\u0418\u0420\u041e\u0412\u0410\u041d\u0418\u042f:\n1. Set test_id = A \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=results, parse_method=primary_json\n2. Set test_id = B \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=review_queue\n3. Set test_id = C \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=monitor_queue\n4. Set test_id = D \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=results, repair_used=true, parse_method=repaired_json\n5. Set test_id = E \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=technical_errors, repair_status=failed\n\n\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0439 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0442\u0435\u0441\u0442\u0435:\n\u2022 test_pass_basic = true\n\u2022 \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430 Google Sheets \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0430\n\u2022 processing_status, parse_method, route\n\n\u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 API \u0414\u041e \u0438 \u041f\u041e\u0421\u041b\u0415.\n\u0422\u0435\u0441\u0442\u044b D \u0438 E \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 Repair API \u2014 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0439 \u0434\u043e\u043f. \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c.\n\u041d\u0415 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0439 workflow \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0443\u0447\u043d\u043e\u0439 \u0437\u0430\u043f\u0443\u0441\u043a.",
"height": 260,
"width": 520,
"color": 3
}
},
{
"id": "rr000000-0000-0000-0000-000000000002",
"name": "Manual Start",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
100,
200
],
"parameters": {}
},
{
"id": "rr000000-0000-0000-0000-000000000003",
"name": "Set Test Selector",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
380,
200
],
"parameters": {
"mode": "manual",
"assignments": {
"assignments": [
{
"id": "rr000000-0000-0000-0000-000000000031",
"name": "test_id",
"value": "A",
"type": "string"
}
]
}
}
},
{
"id": "rr000000-0000-0000-0000-000000000004",
"name": "Select Test Record",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
200
],
"parameters": {
"jsCode": "const testId = String($json.test_id || 'A').toUpperCase();\nconst records = {\n A: {\n test_id: 'A', expected_route: 'results',\n expected_entity_type: 'lead_signal', expected_recommended_action: 'contact',\n expected_quality_range: '80-100', mock_mode: 'none',\n source_type: 'social', platform: 'telegram',\n source_url: 'https://t.me/test_record_A', profile_url: 'https://t.me/user_test_A',\n published_at: '2026-06-05', parsed_at: '2026-06-06',\n text_context: '\u0421\u0440\u043e\u0447\u043d\u043e \u043d\u0443\u0436\u0435\u043d \u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421! Toyota Camry 2019, \u043e\u0442\u043b\u0438\u0447\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u041d\u0443\u0436\u043d\u043e 250 000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0431\u0430\u043d\u043a\u0438 \u0432\u0435\u0437\u0434\u0435 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438 \u0438\u0437-\u0437\u0430 \u0438\u0441\u043f\u043e\u0440\u0447\u0435\u043d\u043d\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438. \u0414\u0435\u043d\u044c\u0433\u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u0437\u0430\u0432\u0442\u0440\u0430 \u0443\u0436\u0435 \u043f\u043e\u0437\u0434\u043d\u043e. \u041c\u043e\u0441\u043a\u0432\u0430, \u0440\u0430\u0439\u043e\u043d \u0412\u044b\u0445\u0438\u043d\u043e. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043b\u0438\u0447\u043a\u0443.'\n },\n B: {\n test_id: 'B', expected_route: 'review_queue',\n expected_entity_type: 'lead_signal', expected_recommended_action: 'investigate',\n expected_quality_range: '35-69', mock_mode: 'none',\n source_type: 'social', platform: 'vk',\n source_url: 'https://vk.com/wall-test_B', profile_url: 'https://vk.com/user_test_B',\n published_at: '2026-06-01', parsed_at: '2026-06-06',\n text_context: '\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0441\u044c \u0437\u0430\u0439\u043c\u0430\u043c\u0438 \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044f. \u0425\u043e\u0447\u0443 \u043f\u043e\u043d\u044f\u0442\u044c \u0443\u0441\u043b\u043e\u0432\u0438\u044f, \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b. \u0415\u0441\u0442\u044c \u043b\u0438 \u0443 \u043a\u043e\u0433\u043e-\u0442\u043e \u043e\u043f\u044b\u0442? \u041f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043e\u043a\u043e\u043b\u043e 100 \u0442\u044b\u0441\u044f\u0447, \u043d\u0435 \u0441\u0440\u043e\u0447\u043d\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0437\u0443\u0447\u0430\u044e.'\n },\n C: {\n test_id: 'C', expected_route: 'monitor_queue',\n expected_entity_type: 'competitor', expected_recommended_action: 'monitor',\n expected_quality_range: '65-90', mock_mode: 'none',\n source_type: 'scraped_web', platform: 'website',\n source_url: 'https://example-competitor-mfo.ru/pts-zalog', profile_url: '',\n published_at: '2026-06-03', parsed_at: '2026-06-06',\n text_context: '\u0417\u0430\u0439\u043c\u044b \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421 \u0438 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044f. \u0421\u0442\u0430\u0432\u043a\u0430 \u043e\u0442 2,5% \u0432 \u043c\u0435\u0441\u044f\u0446. \u041e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435 \u0437\u0430 1 \u0447\u0430\u0441. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441 \u043b\u044e\u0431\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439. \u041c\u043e\u0441\u043a\u0432\u0430 \u0438 \u041c\u041e. \u0421\u0443\u043c\u043c\u0430 \u043e\u0442 50 000 \u0434\u043e 1 000 000 \u0440\u0443\u0431. \u0411\u0435\u0437 \u0441\u043a\u0440\u044b\u0442\u044b\u0445 \u043a\u043e\u043c\u0438\u0441\u0441\u0438\u0439. \u0422\u0435\u043b\u0435\u0444\u043e\u043d: +7(499)555-00-11.'\n },\n D: {\n test_id: 'D', expected_route: 'results',\n expected_entity_type: 'lead_signal', expected_recommended_action: 'contact',\n expected_quality_range: '75-95', mock_mode: 'mock_markdown',\n raw_response_preview: '**\u0410\u043d\u0430\u043b\u0438\u0437 \u0437\u0430\u043f\u0438\u0441\u0438:** \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u0449\u0435\u0442 \u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435, \u0441\u0443\u043c\u043c\u0430 200000 \u0440\u0443\u0431., \u0431\u0430\u043d\u043a\u0438 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438, \u0434\u0435\u043d\u044c\u0433\u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f. \u042d\u0442\u043e lead_signal, recommended_action contact, lead_signal_score 88, quality_score 82, entity_type lead_signal, region \u041c\u043e\u0441\u043a\u0432\u0430, platform telegram, source_type social.',\n source_type: 'social', platform: 'telegram',\n source_url: 'https://t.me/test_record_D', profile_url: 'https://t.me/user_test_D',\n published_at: '2026-06-05', parsed_at: '2026-06-06',\n text_context: '(mock \u2014 primary API bypassed, simulated Markdown response)'\n },\n E: {\n test_id: 'E', expected_route: 'technical_errors',\n expected_entity_type: 'any', expected_recommended_action: 'any',\n expected_quality_range: 'any', mock_mode: 'mock_unrepairable',\n raw_response_preview: '%%%% unreadable broken response no useful market facts %%%%',\n source_type: 'social', platform: 'telegram',\n source_url: 'https://t.me/test_record_E', profile_url: '',\n published_at: '2026-06-05', parsed_at: '2026-06-06',\n text_context: '(mock \u2014 primary API bypassed, simulated unrepairable response)'\n }\n};\nconst rec = records[testId] || records['A'];\nreturn [{ json: rec }];"
}
},
{
"id": "rr000000-0000-0000-0000-000000000005",
"name": "Build Primary Claude Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
940,
200
],
"parameters": {
"jsCode": "const record = $json;\nconst mockMode = record.mock_mode || 'none';\n\nif (mockMode !== 'none') {\n return [{ json: {\n skip_primary_api: true,\n mock_mode: mockMode,\n raw_response_preview: record.raw_response_preview || '',\n original_record: record\n }}];\n}\n\nconst systemPrompt = `You are Marketing Scout Agent v2 -- a market intelligence analyst for a secured lending business in Moscow and Moscow Oblast, Russia.\n\nFor every record ask: What does this mean for the operator's business and what should they do? Reason like a business owner.\n\nANALYSIS PRIORITY ORDER:\n1. LEAD SIGNAL first -- potential client needing secured loan? Moscow/MO? PTS/auto/real estate collateral? Urgency? Contactable?\n2. COMPETITOR second -- active secured lending business Moscow/MO? Threat level?\n3. CONTENT IDEA third -- client fear/objection/knowledge gap for secured lending?\n4. IRRELEVANT if none apply.\n\nIDEAL CLIENT: Car owner (PTS clean title), Moscow/MO, needs cash urgently, bank-rejected, 50k-500k RUB.\n\nHigh-urgency signals (raise lead_signal_score): \u0441\u0440\u043e\u0447\u043d\u043e, \u0441\u0435\u0433\u043e\u0434\u043d\u044f, \u0431\u0430\u043d\u043a\u0438 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438, \u043d\u0435 \u0434\u0430\u044e\u0442 \u043a\u0440\u0435\u0434\u0438\u0442, \u0438\u0441\u043f\u043e\u0440\u0447\u0435\u043d\u0430 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f + specific amount + collateral type.\n\nREGION RULES:\n- Moscow/MO explicit: lead_signal_score 60-100\n- Region ambiguous: eligible up to 55\n- Another city/region: lead_signal_score capped at 40\nCompetitors: Moscow/MO or national coverage -> score normally; other region only -> cap competitor_strength at 50.\n\nlead_signal_score calibration:\n- 85-100: fit + urgency + readiness + Moscow/MO confirmed\n- 70-84: strong fit+urgency, region confirmed, readiness partial\n- 55-69: clear product fit, one signal confirmed, region present\n- 35-54: intent apparent, fit unclear or region outside MO\n- 1-34: no real lead signal\n\nrecommended_action=contact requires lead_signal_score>=70.\n\ncompetitor_strength calibration:\n- 85-100: fresh (<=30d), Moscow/MO, stated rate, same-day, bad-credit accepted, contactable\n- 65-84: active professional, Moscow/MO confirmed, rate absent or one signal missing\n- 45-64: present but older content or uncertain coverage\n- 25-44: weak - stale or different region\n- 1: not a competitor\n\nSKIP RULES -- return status=skipped, quality_score=1, all scores=1 when:\n- Fewer than 40 meaningful chars\n- Pure navigation boilerplate\n- No connection to financial services\n- published_at >180 days before parsed_at with no fresh signals\n\nREASON FIELD (3 sentences required):\n1. WHAT: what is this record, key evidence from text\n2. WHY: why scores are what they are, cite specific signals\n3. NEXT: what operator should do and why\n\nOUTPUT FORMAT -- CRITICAL:\nRespond with ONLY a valid JSON object.\nNo markdown, no code fences, no preamble.\nFirst character must be {. Last must be }.\nAll 25 fields required. Empty string for unknown strings. 1 for unknown integers.\n\nREQUIRED JSON SCHEMA:\n{\n \"created_at\": \"<parsed_at value ISO 8601>\",\n \"source_type\": \"<from input>\",\n \"platform\": \"<from input>\",\n \"source_url\": \"<from input>\",\n \"parsed_at\": \"<from input>\",\n \"published_at\": \"<from input or empty>\",\n \"freshness_status\": \"<fresh|recent|old|unknown>\",\n \"entity_type\": \"<competitor|lead_signal|market_signal|content_idea|irrelevant>\",\n \"company_name\": \"<explicitly in text only or empty>\",\n \"profile_name\": \"<explicitly in text only or empty>\",\n \"profile_url\": \"<from input or empty>\",\n \"region\": \"<explicitly mentioned or empty>\",\n \"service_type\": \"<secured_auto_loan|secured_real_estate_loan|pts_loan|refinancing|mortgage_adjacent|generic_lending|unknown>\",\n \"offer_text\": \"<1 sentence: what offered/sought or content angle title>\",\n \"terms\": \"<explicit rate/conditions only or empty>\",\n \"contact_public\": \"<phone/email/Telegram from text only or empty>\",\n \"text_context\": \"<cleaned summary max 300 chars>\",\n \"detected_need\": \"<lead_signal only: need+amount+urgency+bank rejection+region or empty>\",\n \"competitor_strength\": <integer 1-100; 1 if not competitor>,\n \"lead_signal_score\": <integer 1-100>,\n \"content_idea_score\": <integer 1-100>,\n \"quality_score\": <integer 1-100>,\n \"reason\": \"<3 sentences: what+evidence; why scores; next action>\",\n \"recommended_action\": \"<monitor|contact|create_content|ignore|investigate>\",\n \"status\": \"<analyzed|skipped>\"\n}`;\n\nreturn [{ json: {\n skip_primary_api: false,\n model: 'claude-sonnet-4-6',\n max_tokens: 1400,\n temperature: 0.2,\n system: systemPrompt,\n messages: [{\n role: 'user',\n content: JSON.stringify({\n source_type: record.source_type || '',\n platform: record.platform || '',\n source_url: record.source_url || '',\n profile_url: record.profile_url || '',\n parsed_at: record.parsed_at || '',\n published_at: record.published_at || '',\n text_context: record.text_context || ''\n })\n }]\n}}];"
}
},
{
"id": "rr000000-0000-0000-0000-000000000006",
"name": "IF Skip Primary API?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1220,
200
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "rr000000-0000-0000-0000-000000000061",
"leftValue": "={{ $json.skip_primary_api }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
}
}
},
{
"id": "rr000000-0000-0000-0000-000000000007",
"name": "Claude Primary API Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1500,
60
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "https://aiprimetech.io/v1/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ model: $json.model, max_tokens: $json.max_tokens, temperature: $json.temperature, system: $json.system, messages: $json.messages }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "rr000000-0000-0000-0000-000000000008",
"name": "Parse Primary JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1780,
60
],
"parameters": {
"jsCode": "const response = $json;\nconst testRecord = $('Select Test Record').first().json;\n\n// Handle HTTP error responses\nif (response.error || response.status >= 400) {\n return [{ json: {\n parse_ok: false,\n parse_method: 'primary_json',\n repair_used: false,\n processing_status: 'technical_error_candidate',\n parse_error: 'HTTP error: ' + JSON.stringify(response.error || response.status),\n raw_response_preview: JSON.stringify(response).substring(0, 800),\n original_record: testRecord\n }}];\n}\n\n// Find text item\nlet textItem;\ntry {\n if (!response.content || !Array.isArray(response.content)) throw new Error('No content array');\n textItem = response.content.find(c => c.type === 'text');\n} catch(e) {\n return [{ json: {\n parse_ok: false, parse_method: 'primary_json', repair_used: false,\n processing_status: 'technical_error_candidate',\n parse_error: 'No content array: ' + e.message,\n raw_response_preview: JSON.stringify(response).substring(0, 800),\n original_record: testRecord\n }}];\n}\n\nif (!textItem || !textItem.text) {\n return [{ json: {\n parse_ok: false, parse_method: 'primary_json', repair_used: false,\n processing_status: 'technical_error_candidate',\n parse_error: 'No text item in content array',\n raw_response_preview: JSON.stringify(response.content || response).substring(0, 800),\n original_record: testRecord\n }}];\n}\n\nconst rawPreview = textItem.text.substring(0, 1200);\n\n// Parse with defensive cleanup\nlet parsed;\ntry {\n let txt = textItem.text.trim();\n txt = txt.replace(/^```json\\s*/i, '').replace(/\\s*```$/,'').trim();\n txt = txt.replace(/^```\\s*/,'').replace(/\\s*```$/,'').trim();\n const bStart = txt.indexOf('{');\n const bEnd = txt.lastIndexOf('}');\n if (bStart !== -1 && bEnd !== -1) txt = txt.substring(bStart, bEnd + 1);\n txt = txt.replace(/[\\u2018\\u2019]/g, \"'\").replace(/[\\u201C\\u201D\\u00AB\\u00BB]/g, '\"');\n parsed = JSON.parse(txt);\n} catch(e) {\n return [{ json: {\n parse_ok: false, parse_method: 'primary_json', repair_used: false,\n processing_status: 'technical_error_candidate',\n parse_error: 'JSON.parse failed: ' + e.message,\n raw_response_preview: rawPreview,\n original_record: testRecord\n }}];\n}\n\nreturn [{ json: {\n parse_ok: true,\n parse_method: 'primary_json',\n repair_used: false,\n repair_status: '',\n parse_error: '',\n raw_response_preview: rawPreview,\n ...parsed\n}}];"
}
},
{
"id": "rr000000-0000-0000-0000-000000000009",
"name": "IF Primary Parse OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2060,
60
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "rr000000-0000-0000-0000-000000000091",
"leftValue": "={{ $json.parse_ok }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
}
}
},
{
"id": "rr000000-0000-0000-0000-000000000010",
"name": "Build Repair Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1500,
380
],
"parameters": {
"jsCode": "const input = $json;\nconst testRecord = $('Select Test Record').first().json;\n\nconst rawPreview = input.raw_response_preview || input.original_record?.raw_response_preview || 'empty';\nconst parseErr = input.parse_error || 'Primary API bypassed (mock mode)';\n\nconst repairSystem = `You are a JSON repair formatter. Convert the provided raw model response into one strict JSON object matching the schema below. Rules:\n- Do not add new facts. Do not invent contacts, regions, scores, or terms.\n- Extract only what is explicitly stated in the raw response.\n- If a value is unknown use empty string for text fields and 1 for score fields.\n- If the raw response contains no useful market facts at all: return status=skipped, entity_type=irrelevant, recommended_action=ignore, all scores=1.\n- Return JSON only. No markdown. No explanation. First char must be {, last must be }.\nSchema fields required: created_at, source_type, platform, source_url, parsed_at, published_at, freshness_status, entity_type, company_name, profile_name, profile_url, region, service_type, offer_text, terms, contact_public, text_context, detected_need, competitor_strength, lead_signal_score, content_idea_score, quality_score, reason, recommended_action, status.\nEnum values: entity_type=[competitor,lead_signal,market_signal,content_idea,irrelevant]; recommended_action=[monitor,contact,create_content,ignore,investigate]; status=[analyzed,skipped]; freshness_status=[fresh,recent,old,unknown].`;\n\nconst userMsg = JSON.stringify({\n original_record: {\n source_type: testRecord.source_type || '',\n platform: testRecord.platform || '',\n source_url: testRecord.source_url || '',\n published_at: testRecord.published_at || '',\n parsed_at: testRecord.parsed_at || ''\n },\n raw_primary_response: rawPreview,\n parse_error: parseErr,\n instruction: 'Convert raw_primary_response into the required JSON schema. Return JSON only.'\n});\n\nreturn [{ json: {\n model: 'claude-sonnet-4-6',\n max_tokens: 900,\n temperature: 0.0,\n system: repairSystem,\n messages: [{ role: 'user', content: userMsg }]\n}}];"
}
},
{
"id": "rr000000-0000-0000-0000-000000000011",
"name": "Claude Repair API Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1780,
380
],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "https://aiprimetech.io/v1/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ model: $json.model, max_tokens: $json.max_tokens, temperature: $json.temperature, system: $json.system, messages: $json.messages }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "rr000000-0000-0000-0000-000000000012",
"name": "Parse Repaired JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2060,
380
],
"parameters": {
"jsCode": "const response = $json;\nconst testRecord = $('Select Test Record').first().json;\nconst mockMode = testRecord.mock_mode || 'none';\n\n// Test E: force technical_error for mock_unrepairable\nif (mockMode === 'mock_unrepairable') {\n return [{ json: {\n parse_ok: false,\n parse_method: 'repaired_json',\n repair_used: true,\n repair_status: 'failed',\n processing_status: 'technical_error',\n route: 'technical_errors',\n needs_manual_review: true,\n parse_error: 'Simulated unrepairable response \u2014 forced technical_error for Test E',\n raw_response_preview: testRecord.raw_response_preview || '%%%% unrepairable %%%%',\n test_id: testRecord.test_id,\n expected_route: testRecord.expected_route,\n expected_entity_type: testRecord.expected_entity_type,\n expected_recommended_action: testRecord.expected_recommended_action,\n expected_quality_range: testRecord.expected_quality_range,\n test_pass_basic: true,\n test_notes: 'mock_unrepairable forced technical_error as expected'\n }}];\n}\n\n// Handle HTTP error from repair call\nif (response.error || response.status >= 400) {\n return [{ json: {\n parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n needs_manual_review: true,\n parse_error: 'Repair HTTP error: ' + JSON.stringify(response.error || response.status),\n raw_response_preview: JSON.stringify(response).substring(0, 500)\n }}];\n}\n\n// Find text item\nlet textItem;\ntry {\n textItem = (response.content || []).find(c => c.type === 'text');\n} catch(e) { textItem = null; }\n\nif (!textItem || !textItem.text) {\n return [{ json: {\n parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n needs_manual_review: true,\n parse_error: 'No text item in repair response',\n raw_response_preview: JSON.stringify(response.content || response).substring(0, 500)\n }}];\n}\n\nconst rawPreview = textItem.text.substring(0, 1200);\n\nlet parsed;\ntry {\n let txt = textItem.text.trim();\n txt = txt.replace(/^```json\\s*/i,'').replace(/\\s*```$/,'').trim();\n txt = txt.replace(/^```\\s*/,'').replace(/\\s*```$/,'').trim();\n const bStart = txt.indexOf('{'); const bEnd = txt.lastIndexOf('}');\n if (bStart !== -1 && bEnd !== -1) txt = txt.substring(bStart, bEnd + 1);\n txt = txt.replace(/[\\u2018\\u2019]/g,\"'\").replace(/[\\u201C\\u201D\\u00AB\\u00BB]/g,'\"');\n parsed = JSON.parse(txt);\n} catch(e) {\n return [{ json: {\n parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n needs_manual_review: true,\n parse_error: 'Repair JSON.parse failed: ' + e.message,\n raw_response_preview: rawPreview\n }}];\n}\n\nconst parseMethod = (mockMode === 'mock_markdown') ? 'mock_markdown_repair' : 'repaired_json';\n\nreturn [{ json: {\n parse_ok: true,\n parse_method: parseMethod,\n repair_used: true,\n repair_status: 'success',\n parse_error: '',\n raw_response_preview: rawPreview,\n ...parsed\n}}];"
}
},
{
"id": "rr000000-0000-0000-0000-000000000013",
"name": "Normalize + Route",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2340,
200
],
"parameters": {
"jsCode": "const data = $json;\nconst testRecord = $('Select Test Record').first().json;\n\nfunction clamp(v) { const n = parseInt(v); return isNaN(n) ? 1 : Math.min(100, Math.max(1, n)); }\n\n// Normalize free-text service_type into allowed enum values\nfunction normalizeServiceType(raw, hay) {\n const allowed = ['secured_auto_loan','secured_real_estate_loan','pts_loan','refinancing','mortgage_adjacent','generic_lending'];\n const r = (raw || '').toLowerCase().trim();\n if (allowed.includes(r)) return r;\n const s = r + ' ' + (hay || '');\n if (s.includes('\u043f\u0442\u0441') || s.includes('pts')) return 'pts_loan';\n if ((s.includes('\u0430\u0432\u0442\u043e') || s.includes('\u043c\u0430\u0448\u0438\u043d')) && (s.includes('\u0437\u0430\u043b\u043e\u0433') || s.includes('collateral') || s.includes('\u043b\u043e\u043c\u0431\u0430\u0440\u0434'))) return 'secured_auto_loan';\n if (s.includes('\u043d\u0435\u0434\u0432\u0438\u0436') || s.includes('\u043a\u0432\u0430\u0440\u0442\u0438\u0440') || s.includes('\u0434\u043e\u043c') || s.includes('\u0437\u0435\u043c\u043b')) return 'secured_real_estate_loan';\n if (s.includes('\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441') || s.includes('refinanc')) return 'refinancing';\n if (s.includes('\u0438\u043f\u043e\u0442\u0435\u043a') || s.includes('mortgage')) return 'mortgage_adjacent';\n if (s.includes('\u0431\u0438\u0437\u043d\u0435\u0441') || s.includes('business')) {\n if (s.includes('\u043d\u0435\u0434\u0432\u0438\u0436') || s.includes('\u043a\u0432\u0430\u0440\u0442\u0438\u0440')) return 'secured_real_estate_loan';\n return 'generic_lending';\n }\n return 'unknown';\n}\n\n// Descriptive company_name fallback (never invent a brand)\nfunction companyNameFallback(existing, entity, hay) {\n if (existing && String(existing).trim() !== '') return existing;\n if (entity === 'competitor' && (hay.includes('\u043c\u0444\u043e') || hay.includes('\u043c\u0438\u043a\u0440\u043e\u0444\u0438\u043d\u0430\u043d\u0441') || hay.includes('mfo'))) return '\u041c\u0424\u041e / \u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440';\n if (hay.includes('\u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u0438\u043d\u0432\u0435\u0441\u0442\u043e\u0440')) return '\u0427\u0430\u0441\u0442\u043d\u044b\u0439 \u0438\u043d\u0432\u0435\u0441\u0442\u043e\u0440';\n if (hay.includes('\u0430\u0432\u0442\u043e\u043b\u043e\u043c\u0431\u0430\u0440\u0434')) return '\u0410\u0432\u0442\u043e\u043b\u043e\u043c\u0431\u0430\u0440\u0434';\n if (hay.includes('\u0431\u0440\u043e\u043a\u0435\u0440')) return '\u0411\u0440\u043e\u043a\u0435\u0440';\n if (entity === 'competitor') return '\u041a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442 \u0431\u0435\u0437 \u0431\u0440\u0435\u043d\u0434\u0430';\n return '';\n}\n\n// Pass-through for confirmed technical_error (from Parse Repaired JSON)\nif (data.processing_status === 'technical_error' && data.route === 'technical_errors') {\n const expRoute = testRecord.expected_route || '';\n return [{ json: {\n ...data,\n created_at: data.created_at || testRecord.parsed_at || '',\n source_type: data.source_type || testRecord.source_type || '',\n platform: data.platform || testRecord.platform || '',\n source_url: data.source_url || testRecord.source_url || '',\n parsed_at: data.parsed_at || testRecord.parsed_at || '',\n published_at: data.published_at || testRecord.published_at || '',\n test_id: testRecord.test_id,\n expected_route: testRecord.expected_route,\n expected_entity_type: testRecord.expected_entity_type,\n expected_recommended_action: testRecord.expected_recommended_action,\n expected_quality_range: testRecord.expected_quality_range,\n actual_entity_type: data.entity_type || '',\n actual_recommended_action: data.recommended_action || '',\n actual_quality_score: data.quality_score || 1,\n actual_lead_signal_score: data.lead_signal_score || 1,\n actual_content_idea_score: data.content_idea_score || 1,\n actual_competitor_strength: data.competitor_strength || 1,\n test_pass_basic: data.test_pass_basic !== undefined ? data.test_pass_basic : (data.route === expRoute),\n test_notes: data.test_notes || ('route=' + data.route + '; expected=' + expRoute)\n }}];\n}\n\n// Normalize schema fields\nconst entity = data.entity_type || 'irrelevant';\nconst status = data.status || 'analyzed';\nconst recAction = data.recommended_action || 'ignore';\nconst leadScore = clamp(data.lead_signal_score);\nconst compScore = clamp(data.competitor_strength);\nconst contentScore = clamp(data.content_idea_score);\nconst qualScore = clamp(data.quality_score);\n\nconst validEntities = ['competitor','lead_signal','market_signal','content_idea','irrelevant'];\nconst validActions = ['monitor','contact','create_content','ignore','investigate'];\nconst safeEntity = validEntities.includes(entity) ? entity : 'irrelevant';\nconst safeAction = validActions.includes(recAction) ? recAction : 'ignore';\nconst safeStatus = (status === 'skipped') ? 'skipped' : 'analyzed';\n\n// Derived text haystacks for normalization and routing keyword checks\nconst hay = ((data.text_context||'') + ' ' + (data.offer_text||'') + ' ' + (data.detected_need||'') + ' ' + (data.reason||'') + ' ' + (data.service_type||'') + ' ' + (testRecord.text_context||'')).toLowerCase();\nconst companyHay = ((data.text_context||'') + ' ' + (data.reason||'') + ' ' + (data.offer_text||'') + ' ' + (data.source_url||testRecord.source_url||'') + ' ' + (data.detected_need||'')).toLowerCase();\n\nconst safeServiceType = normalizeServiceType(data.service_type, hay);\nconst safeCompanyName = companyNameFallback(data.company_name, safeEntity, companyHay);\n\n// Determine processing_status\nlet procStatus = 'parsed_success';\nif (safeStatus === 'skipped' || safeEntity === 'irrelevant') procStatus = 'business_skip';\n\n// Weak / potential lead detection (must run before content_queue routing)\nconst srcType = (data.source_type || testRecord.source_type || '').toLowerCase();\nconst isSocialOrClassified = (srcType === 'social' || srcType === 'classified');\nconst productTerms = ['loan','collateral','pts','auto','real estate','refinanc','\u0437\u0430\u0439\u043c','\u043a\u0440\u0435\u0434\u0438\u0442','\u0437\u0430\u043b\u043e\u0433','\u043f\u0442\u0441','\u0430\u0432\u0442\u043e','\u043c\u0430\u0448\u0438\u043d','\u043d\u0435\u0434\u0432\u0438\u0436','\u043a\u0432\u0430\u0440\u0442\u0438\u0440','\u0437\u0435\u043c\u043b','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441','\u0438\u043f\u043e\u0442\u0435\u043a'];\nconst mentionsProduct = productTerms.some(t => hay.includes(t));\n\nconst weakLead = (\n (safeEntity === 'lead_signal' && leadScore >= 30 && leadScore <= 69) ||\n (safeAction === 'investigate') ||\n (leadScore >= 30 && isSocialOrClassified && mentionsProduct) ||\n (safeEntity === 'content_idea' && leadScore >= 30 && isSocialOrClassified && safeServiceType !== 'unknown')\n);\n\n// Routing priority:\n// 1 technical_errors (handled above) 2 skipped/irrelevant 3 hot lead 4 weak lead\n// 5 competitor 6 pure content idea 7 fallback review_queue\nlet route;\nlet needsReview;\n\nif (safeStatus === 'skipped' || safeEntity === 'irrelevant') {\n route = 'skipped_log'; needsReview = false;\n} else if (safeEntity === 'lead_signal' && leadScore >= 70 && safeAction === 'contact') {\n route = 'results'; needsReview = false;\n} else if (weakLead) {\n route = 'review_queue'; needsReview = true;\n} else if (safeEntity === 'competitor' && compScore >= 45) {\n route = 'monitor_queue'; needsReview = false;\n} else if (safeEntity === 'content_idea' && contentScore >= 50) {\n route = 'content_queue'; needsReview = true;\n} else {\n route = 'review_queue'; needsReview = true;\n}\n\n// Safety: enforce route is exactly one of the six valid sheet tabs\nconst validRoutes = ['results','review_queue','monitor_queue','content_queue','skipped_log','technical_errors'];\nlet parseError = data.parse_error || '';\nif (!validRoutes.includes(route)) {\n route = 'technical_errors';\n procStatus = 'technical_error';\n needsReview = true;\n parseError = (parseError ? parseError + '; ' : '') + 'invalid_route';\n}\n\nconst expRoute = testRecord.expected_route || '';\nconst expEntity = testRecord.expected_entity_type || 'any';\n\n// Test pass logic: route-focused for review_queue (entity may legitimately\n// differ after repair); strict entity match for other routes\nlet testPass;\nif (expRoute === 'review_queue') {\n testPass = (route === 'review_queue') && (needsReview === true) && (leadScore >= 30);\n} else {\n testPass = (route === expRoute) && (expEntity === 'any' || safeEntity === expEntity);\n}\n\nreturn [{ json: {\n created_at: data.created_at || testRecord.parsed_at || '',\n source_type: data.source_type || testRecord.source_type || '',\n platform: data.platform || testRecord.platform || '',\n source_url: data.source_url || testRecord.source_url || '',\n parsed_at: data.parsed_at || testRecord.parsed_at || '',\n published_at: data.published_at || testRecord.published_at || '',\n freshness_status: data.freshness_status || 'unknown',\n entity_type: safeEntity,\n company_name: safeCompanyName,\n profile_name: data.profile_name || '',\n profile_url: data.profile_url || testRecord.profile_url || '',\n region: data.region || '',\n service_type: safeServiceType,\n offer_text: data.offer_text || '',\n terms: data.terms || '',\n contact_public: data.contact_public || '',\n text_context: data.text_context || '',\n detected_need: data.detected_need || '',\n competitor_strength: compScore,\n lead_signal_score: leadScore,\n content_idea_score: contentScore,\n quality_score: qualScore,\n reason: data.reason || '',\n recommended_action: safeAction,\n status: safeStatus,\n processing_status: procStatus,\n parse_method: data.parse_method || 'primary_json',\n parse_error: parseError,\n raw_response_preview: data.raw_response_preview || '',\n route: route,\n needs_manual_review: needsReview,\n source_record_type: testRecord.source_type || '',\n repair_used: data.repair_used || false,\n repair_status: data.repair_status || '',\n test_id: testRecord.test_id,\n expected_route: testRecord.expected_route,\n expected_entity_type: testRecord.expected_entity_type,\n expected_recommended_action: testRecord.expected_recommended_action,\n expected_quality_range: testRecord.expected_quality_range,\n actual_entity_type: safeEntity,\n actual_recommended_action: safeAction,\n actual_quality_score: qualScore,\n actual_lead_signal_score: leadScore,\n actual_content_idea_score: contentScore,\n actual_competitor_strength: compScore,\n test_pass_basic: testPass,\n test_notes: 'route=' + route + '; entity=' + safeEntity + '; lead=' + leadScore + '; comp=' + compScore + '; content=' + contentScore + '; action=' + safeAction + '; service=' + safeServiceType + '; company=' + safeCompanyName + '; weakLead=' + weakLead + '; parse=' + (data.parse_method || '') + '; repair=' + (data.repair_used || false) + '; expected_route=' + expRoute\n}}];\n"
}
},
{
"id": "rr000000-0000-0000-0000-000000000099",
"name": "Append to Dynamic Route Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
2620,
200
],
"parameters": {
"authentication": "serviceAccount",
"operation": "append",
"documentId": {
"__rl": true,
"value": "PASTE_SPREADSHEET_ID_HERE",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "={{ $json.route }}",
"mode": "name"
},
"columns": {
"mappingMode": "autoMapInputData",
"value": {},
"matchingColumns": [],
"schema": []
},
"options": {}
},
"credentials": {
"googleApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"onError": "continueRegularOutput",
"alwaysOutputData": true
}
],
"connections": {
"Manual Start": {
"main": [
[
{
"node": "Set Test Selector",
"type": "main",
"index": 0
}
]
]
},
"Set Test Selector": {
"main": [
[
{
"node": "Select Test Record",
"type": "main",
"index": 0
}
]
]
},
"Select Test Record": {
"main": [
[
{
"node": "Build Primary Claude Request",
"type": "main",
"index": 0
}
]
]
},
"Build Primary Claude Request": {
"main": [
[
{
"node": "IF Skip Primary API?",
"type": "main",
"index": 0
}
]
]
},
"IF Skip Primary API?": {
"main": [
[
{
"node": "Build Repair Request",
"type": "main",
"index": 0
}
],
[
{
"node": "Claude Primary API Request",
"type": "main",
"index": 0
}
]
]
},
"Claude Primary API Request": {
"main": [
[
{
"node": "Parse Primary JSON",
"type": "main",
"index": 0
}
]
]
},
"Parse Primary JSON": {
"main": [
[
{
"node": "IF Primary Parse OK?",
"type": "main",
"index": 0
}
]
]
},
"IF Primary Parse OK?": {
"main": [
[
{
"node": "Normalize + Route",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Repair Request",
"type": "main",
"index": 0
}
]
]
},
"Build Repair Request": {
"main": [
[
{
"node": "Claude Repair API Request",
"type": "main",
"index": 0
}
]
]
},
"Claude Repair API Request": {
"main": [
[
{
"node": "Parse Repaired JSON",
"type": "main",
"index": 0
}
]
]
},
"Parse Repaired JSON": {
"main": [
[
{
"node": "Normalize + Route",
"type": "main",
"index": 0
}
]
]
},
"Normalize + Route": {
"main": [
[
{
"node": "Append to Dynamic Route Sheet",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "rr000000-resilient-router-v003-dynamic-patched-20260606"
}
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.
googleApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
02 - Claude API Single Record Analysis v2 RESILIENT ROUTER TEST DYNAMIC SHEET. Uses httpRequest, googleSheets. Event-driven trigger; 15 nodes.
Source: https://github.com/CodeVinci8/vinci-ai-pilot/blob/main/n8n/workflows/02_claude_api_single_record_v2_resilient_router_test_dynamic_sheet.json — 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.
TrackCollect_deeper. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 80 nodes.
This template is ideal for solo store owners, eCommerce marketers, automation beginners, or anyone using Shopify and Gmail who wants to recover lost revenue without coding.
PCN. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 60 nodes.
Product Ad Machine. Uses googleDrive, googleSheets, httpRequest, @ffmpeg-micro/n8n-nodes-ffmpeg-micro. Event-driven trigger; 60 nodes.
Basic AI SEO KW Research n8n DataForSEO Gumroad 030125. Uses googleSheets, googleDrive, httpRequest. Event-driven trigger; 56 nodes.