{ "meta": { "instanceId": "0a70652f43c1b29dd16c35b61a38fd31c8004f58bc7e723bf43262a797407c77", "templateCredsSetupCompleted": true }, "name": "My workflow 5", "tags": [], "nodes": [ { "id": "850d04e5-6cad-4a35-96cf-47478ae7b91f", "name": "Convert to File", "type": "n8n-nodes-base.convertToFile", "position": [ 880, 0 ], "parameters": { "options": {} }, "typeVersion": 1.1 }, { "id": "a7f50841-af97-4127-a4f1-a0ce4352759e", "name": "Simple Memory", "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", "position": [ -880, 270 ], "parameters": { "contextWindowLength": 100 }, "typeVersion": 1.3 }, { "id": "b30bbfbc-747c-4ba7-8ae5-737bb13bb6a3", "name": "When chat message received", "type": "@n8n/n8n-nodes-langchain.chatTrigger", "position": [ -1440, 175 ], "webhookId": "36280bdf-bc14-42f0-86f8-281ce2885df6", "parameters": { "options": {} }, "typeVersion": 1.1 }, { "id": "d3a27151-8278-4c72-aa76-a1a31095a240", "name": "AI Agent", "type": "@n8n/n8n-nodes-langchain.agent", "position": [ -908, 50 ], "parameters": { "text": "={{ $json.combinedInput }}", "options": { "maxIterations": 100, "systemMessage": "=You are an expert in interpreting natural language queries and converting them into structured JSON requests for the Explorium MCP API.\n\nYour task is to generate the **`data` portion** of a `fetch_prospects` API request, using only Explorium MCP-compatible filters and parameters. Adjust the filters based on both the query and the MCP specifications—not all filters are relevant for every query.\n\n**You may also be asked to revise a previous response that failed validation due to incorrect filters or formatting.** In such cases, carefully review the error message and regenerate the JSON accordingly.\n\nYour response must be a **single JSON object** with this structure:\n\n```json\n{\n \"mode\": \"full\",\n \"size\": 10000,\n \"page_size\": 100,\n \"page\": 1,\n \"filters\": {\n \"has_email\": { \"value\": true },\n \"has_phone_number\": { \"value\": true },\n \"job_level\": { \"values\": [...] },\n \"job_department\": { \"values\": [...] },\n \"business_id\": { \"values\": [...] },\n ...\n }\n}\n```\n\n### Required Structure:\n\n* The fields `mode`, `size`, `page_size`, and `page` must appear **before** the `filters` object.\n* Default values:\n\n * `mode`: `\"full\"`\n * `size`: `10000` (unless otherwise specified by the user; must remain between 1 and 10000)\n * `page_size`: `100` (**must always be less or equal to `size`**)\n * `page`: `1`\n * `has_email`: always `{ \"value\": true }`, unless explicitly requested otherwise\n\n---\n\n### Allowed Filters & Valid Values:\n\nIf any of the following filters appear in your output, their values **must strictly match** these lists:\n\n* **`company_size`**: `'1-10'`, `'11-50'`, `'51-200'`, `'201-500'`, `'501-1000'`, `'1001-5000'`, `'5001-10000'`, `'10001+'`\n* **`company_age`**: `'0-3'`, `'3-6'`, `'6-10'`, `'10-20'`, `'20+'`\n* **`number_of_locations`**: `'0-1'`, `'2-5'`, `'6-20'`, `'21-50'`, `'51-100'`, `'101-1000'`, `'1001+'`\n* **`company_revenue`**: `'0-500K'`, `'500K-1M'`, `'1M-5M'`, `'5M-10M'`, `'10M-25M'`, `'25M-75M'`, `'75M-200M'`, `'200M-500M'`, `'500M-1B'`, `'1B-10B'`, `'10B-100B'`, `'100B-1T'`, `'1T-10T'`, `'10T+'`\n* **`job_level`**: `'director'`, `'manager'`, `'vp'`, `'partner'`, `'cxo'`, `'non-managerial'`, `'senior'`, `'entry'`, `'training'`, `'unpaid'`\n* **`job_department`**: `'customer service'`, `'design'`, `'education'`, `'engineering'`, `'finance'`, `'general'`, `'health'`, `'human resources'`, `'legal'`, `'marketing'`, `'media'`, `'operations'`, `'public relations'`, `'real estate'`, `'sales'`, `'trades'`, `'unknown'`\n* **`country_code`** and **`company_country_code`**: Must be valid **2-letter ISO Alpha-2 codes** (e.g., `us`, `gb`, `de`, etc.)\n* **`total_experience_months`** and **`current_role_months`**: These must be objects with both a `gte` and `lte` key where `gte <= lte`, for example:\n\n```json\n\"total_experience_months\": { \"gte\": 3, \"lte\": 6 }\n```\n\nand\n\n```json\n\"current_role_months\": { \"gte\": 1, \"lte\": 10 }\n```\n\n* If a company name is mentioned (e.g., \"Microsoft\"), include its `business_id` (use a placeholder like `\"abc123\"` if unknown)\n\n---\n\n### Interpretation & Mapping:\n\n* Extract job levels from descriptions like:\n\n * “executives” or “leadership” → `\"cxo\"`\n * “heads” or “vice presidents” → `\"vp\"`\n* Map department-like terms (e.g., “sales”, “marketing”) to the `job_department` field.\n* Don’t include filters with empty or unrecognized values.\n* Use plural logic: if the query includes a phrase like “finance and marketing,” return both departments.\n\n---\n\n### Off-topic Handling:\n\nIf the request is unrelated to B2B data intelligence or the MCP use cases, return the following message verbatim:\n\n> I’m the Explorium MCP Playground assistant, built to explore company & prospect intelligence.\n> Example queries:\n> • Find SaaS firms in New York with 50‑200 employees\n> • Show Microsoft’s technology stack\n> • Get marketing‑director contacts at healthcare companies\n> • Compare funding rounds of fintech startups\n> How would you like to explore Explorium’s data?" }, "promptType": "define", "hasOutputParser": true }, "retryOnFail": true, "typeVersion": 1.7 }, { "id": "24b27ab1-ba9e-4e43-924f-819a1505cdba", "name": "Extract \"data\"", "type": "n8n-nodes-base.splitOut", "position": [ 440, 0 ], "parameters": { "options": {}, "fieldToSplitOut": "data" }, "typeVersion": 1 }, { "id": "8cc673a5-d6cf-4e64-9d99-01d0320429fa", "name": "Merge All Pages", "type": "n8n-nodes-base.code", "position": [ 220, 0 ], "parameters": { "jsCode": "// Initialize a single array to hold all data items\nconst mergedData = [];\n\n// Loop over all input items\nfor (const item of $input.all()) {\n if (item.json.data && Array.isArray(item.json.data)) {\n mergedData.push(...item.json.data); // Spread and merge the data arrays\n }\n}\n\n// Return a single item with the combined data array\nreturn [\n {\n json: {\n data: mergedData\n }\n }\n];" }, "typeVersion": 2 }, { "id": "e9173b61-010d-42f9-83d9-1ce724ca9fb9", "name": "Prepare for CSV", "type": "n8n-nodes-base.code", "position": [ 660, 0 ], "parameters": { "jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n items.push({\n prospect_id: item.json.prospect_id ?? null,\n first_name: item.json.first_name ?? null,\n last_name: item.json.last_name ?? null,\n full_name: item.json.full_name ?? null,\n country_name: item.json.country_name ?? null,\n region_name: item.json.region_name ?? null,\n city: item.json.city ?? null,\n linkedin: item.json.linkedin ?? null,\n experience: Array.isArray(item.json.experience) ? item.json.experience.join('; ') : null,\n skills: Array.isArray(item.json.skills) ? item.json.skills.join(', ') : null,\n interests: Array.isArray(item.json.interests) ? item.json.interests.join(', ') : item.json.interests ?? null,\n company_name: item.json.company_name ?? null,\n company_website: item.json.company_website ?? null,\n company_linkedin: item.json.company_linkedin ?? null,\n job_department: item.json.job_department ?? null,\n job_seniority_level: Array.isArray(item.json.job_seniority_level) ? item.json.job_seniority_level.join(', ') : null,\n job_title: item.json.job_title ?? null,\n business_id: item.json.business_id ?? null,\n });\n}\n\nreturn items;" }, "typeVersion": 2 }, { "id": "6ce4650b-b267-4642-9104-0e47568ee049", "name": "API Call Validation", "type": "n8n-nodes-base.code", "position": [ -440, 50 ], "parameters": { "jsCode": "const allowedFilters = [\n \"country_code\",\n \"region_country_code\",\n \"company_country_code\",\n \"company_region_country_code\",\n \"company_size\",\n \"company_revenue\",\n \"company_age\",\n \"google_category\",\n \"naics_category\",\n \"linkedin_category\",\n \"company_name\",\n \"number_of_locations\",\n \"city_region_country\",\n \"website_keywords\",\n \"has_email\",\n \"has_phone_number\",\n \"job_level\",\n \"job_department\",\n \"job_title\",\n \"business_id\",\n \"total_experience_months\",\n \"current_role_months\"\n];\n\nconst validCompanySizes = ['1-10', '11-50', '51-200', '201-500', '501-1000', '1001-5000', '5001-10000', '10001+'];\nconst validAges = ['0-3', '3-6', '6-10', '10-20', '20+'];\nconst validLocations = ['0-1', '2-5', '6-20', '21-50', '51-100', '101-1000', '1001+'];\nconst validRevenue = [\n \"0-500K\", \"500K-1M\", \"1M-5M\", \"5M-10M\", \"10M-25M\", \"25M-75M\", \"75M-200M\", \"200M-500M\",\n \"500M-1B\", \"1B-10B\", \"10B-100B\", \"100B-1T\", \"1T-10T\", \"10T+\"\n];\nconst validJobLevel = [\"director\", \"manager\", \"vp\", \"partner\", \"cxo\", \"non-managerial\", \"senior\", \"entry\", \"training\", \"unpaid\"];\nconst validDepartment = [\n \"customer service\", \"design\", \"education\", \"engineering\", \"finance\", \"general\", \"health\", \"human resources\",\n \"legal\", \"marketing\", \"media\", \"operations\", \"public relations\", \"real estate\", \"sales\", \"trades\", \"unknown\"\n];\n\nconst countryCodeRegex = /^(aw|af|ao|ai|ax|al|ad|ae|ar|am|as|aq|tf|ag|au|at|az|bi|be|bj|bq|bf|bd|bg|bh|bs|ba|bl|by|bz|bm|bo|br|bb|bn|bt|bv|bw|cf|ca|cc|ch|cl|cn|ci|cm|cd|cg|ck|co|km|cv|cr|cu|cw|cx|ky|cy|cz|de|dj|dm|dk|do|dz|ec|eg|er|eh|es|ee|et|fi|fj|fk|fr|fo|fm|ga|gb|ge|gg|gh|gi|gn|gp|gm|gw|gq|gr|gd|gl|gt|gf|gu|gy|hk|hm|hn|hr|ht|hu|id|im|in|io|ie|ir|iq|is|il|it|jm|je|jo|jp|kz|ke|kg|kh|ki|kn|kr|kw|la|lb|lr|ly|lc|li|lk|ls|lt|lu|lv|mo|mf|ma|mc|md|mg|mv|mx|mh|mk|ml|mt|mm|me|mn|mp|mz|mr|ms|mq|mu|mw|my|yt|na|nc|ne|nf|ng|ni|nu|nl|no|np|nr|nz|om|pk|pa|pn|pe|ph|pw|pg|pl|pr|kp|pt|py|ps|pf|qa|re|ro|ru|rw|sa|sd|sn|sg|gs|sh|sj|sb|sl|sv|sm|so|pm|rs|ss|st|sr|sk|si|se|sz|sx|sc|sy|tc|td|tg|th|tj|tk|tm|tl|to|tt|tn|tr|tv|tw|tz|ug|ua|um|uy|us|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xk|ye|za|zm|zw)$/;\n\nconst item = $input.first().json;\nconst output = item.output || {};\nconst filters = output.filters || {};\nconst errors = [];\n\nfor (const key of Object.keys(filters)) {\n const filter = filters[key];\n\n if (!allowedFilters.includes(key)) {\n errors.push(`Invalid filter key: '${key}'`);\n continue;\n }\n\n if ('value' in filter) {\n if (typeof filter.value !== 'boolean') {\n errors.push(`Filter '${key}' has a 'value' but it's not a boolean.`);\n }\n continue;\n }\n\n if ('values' in filter) {\n if (!Array.isArray(filter.values) || filter.values.length === 0) {\n errors.push(`Filter '${key}' must contain a non-empty 'values' array.`);\n continue;\n }\n\n // Normalize to lowercase for fields that require it\n if ([\"job_level\", \"job_department\", \"country_code\", \"region_country_code\", \"company_country_code\", \"company_region_country_code\"].includes(key)) {\n filter.values = filter.values.map(v => typeof v === 'string' ? v.toLowerCase() : v);\n }\n\n const validateValues = (validList, keyName) => {\n const invalid = filter.values.filter(v => !validList.includes(v));\n if (invalid.length > 0) {\n errors.push(`Invalid ${keyName} values: ${invalid.join(', ')}. Valid values are: ${validList.join(', ')}`);\n }\n };\n\n switch (key) {\n case 'company_size':\n validateValues(validCompanySizes, 'company_size');\n break;\n case 'company_age':\n validateValues(validAges, 'company_age');\n break;\n case 'number_of_locations':\n validateValues(validLocations, 'number_of_locations');\n break;\n case 'company_revenue':\n validateValues(validRevenue, 'company_revenue');\n break;\n case 'job_level':\n validateValues(validJobLevel, 'job_level');\n break;\n case 'job_department':\n validateValues(validDepartment, 'job_department');\n break;\n case 'country_code':\n const invalid = filter.values.filter(v => !countryCodeRegex.test(v));\n if (invalid.length > 0) {\n errors.push(`Invalid country_code values: ${invalid.join(', ')}. Must be ISO 3166-1 alpha-2 codes.`);\n }\n break;\n }\n\n const unique = new Set(filter.values);\n if (unique.size < filter.values.length) {\n errors.push(`Duplicate values found in filter '${key}'`);\n }\n\n continue;\n }\n\n // Handle total_experience_months and current_role_months as a range objects\n if (key === 'total_experience_months' || key === 'current_role_months') {\n const range = filter;\n \n if (!('gte' in range) && !('lte' in range)) {\n errors.push(`Filter '${key}' must include at least 'gte' or 'lte'.`);\n continue;\n }\n\n if ('gte' in range && typeof range.gte !== 'number') {\n errors.push(`Filter '${key}' has a non-numeric 'gte' value.`);\n }\n\n if ('lte' in range && typeof range.lte !== 'number') {\n errors.push(`Filter '${key}' has a non-numeric 'lte' value.`);\n }\n\n if ('gte' in range && 'lte' in range && range.gte > range.lte) {\n errors.push(`Filter '${key}' has 'gte' greater than 'lte'.`);\n }\n\n continue;\n}\n\n errors.push(`Filter '${key}' must include either 'value', 'values', or a valid range.`);\n}\n\nreturn [\n {\n json: {\n ...item,\n isValid: errors.length === 0,\n validationErrors: errors\n }\n }\n];\n" }, "typeVersion": 2 }, { "id": "fdfc7b79-9dbe-4fcb-89c0-4b83d9c11915", "name": "Validation Prompter", "type": "n8n-nodes-base.code", "position": [ 0, 275 ], "parameters": { "jsCode": "const sessionId = $('When chat message received').first().json.sessionId;\nconst userQuery = $('When chat message received').first().json.chatInput;\nconst aiOutput = $('AI Agent').first().json.output;\nconst validationErrors = $('API Call Validation').first().json.validationErrors;\n\n// Safely stringify output and errors\nconst formattedOutput = typeof aiOutput === 'object' ? JSON.stringify(aiOutput, null, 2) : aiOutput;\nconst formattedErrors = Array.isArray(validationErrors) ? validationErrors.join('\\n- ') : validationErrors;\n\nreturn [\n {\n sessionId,\n action: \"sendMessage\",\n source: \"validation\",\n errorInput: \n`Your response did not pass validation.\n\n📝 **User Query**:\n${userQuery}\n\n🤖 **Your Output**:\n${formattedOutput}\n\n⚠️ **Validation Errors**:\n- ${formattedErrors}\n\n🔧 Please review the validation errors and regenerate a corrected output. \nEnsure that all values strictly match the allowed formats and valid categories.\n\nReformulate your response accordingly.`\n }\n];" }, "typeVersion": 2 }, { "id": "60425530-b675-4389-9712-f86071453d58", "name": "Is API Call Valid?", "type": "n8n-nodes-base.if", "position": [ -220, 50 ], "parameters": { "options": {}, "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "9b2c3127-07c4-47d0-8dc3-44daa2a2d6e7", "operator": { "type": "boolean", "operation": "true", "singleValue": true }, "leftValue": "={{ $json.isValid }}", "rightValue": "true" } ] } }, "typeVersion": 2.2 }, { "id": "436aff8c-046d-4060-9a26-00b259e2ffbf", "name": "Chat or Refinement", "type": "n8n-nodes-base.code", "position": [ -1220, 175 ], "parameters": { "jsCode": "const chat = $json.chatInput;\nconst error = $json.errorInput;\n\nreturn [\n {\n json: {\n ...$json,\n combinedInput: error ?? chat\n }\n }\n];" }, "typeVersion": 2 }, { "id": "7e0953ff-683c-4b6d-8f7d-abb80abd15ad", "name": "Anthropic Chat Model", "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", "position": [ -1000, 270 ], "parameters": { "model": { "__rl": true, "mode": "list", "value": "claude-sonnet-4-20250514", "cachedResultName": "Claude Sonnet 4" }, "options": { "thinking": false } }, "credentials": { "anthropicApi": { "id": "zb4GaoxVQ7gIbD2A", "name": "Anthropic account 3" } }, "typeVersion": 1.3 }, { "id": "8554cc57-0118-4af4-96d0-ac2a9a7c7f2c", "name": "Explorium MCP", "type": "@n8n/n8n-nodes-langchain.mcpClientTool", "position": [ -760, 270 ], "parameters": { "sseEndpoint": "https://mcp-auth.explorium.ai/sse", "authentication": "headerAuth" }, "credentials": { "httpHeaderAuth": { "id": "85mkGmNNdK1951hF", "name": "Header Auth Connection" } }, "typeVersion": 1 }, { "id": "e6c05002-29c1-4716-bc14-20361fb28e56", "name": "Output Parser", "type": "@n8n/n8n-nodes-langchain.outputParserStructured", "position": [ -640, 270 ], "parameters": { "jsonSchemaExample": "{\n \"mode\": \"full\",\n \"size\": 10000,\n \"page_size\": 100,\n \"page\": 1,\n \"filters\": {\n \"has_email\": {\n \"value\": true\n },\n \"has_phone_number\": {\n \"value\": true\n },\n \"job_level\": {\n \"values\": [\n \"cxo\",\n \"manager\"\n ]\n },\n \"job_department\": {\n \"values\": [\n \"Customer service\",\n \"Engineering\"\n ]\n },\n \"business_id\": {\n \"values\": [\n \"8adce3ca1cef0c986b22310e369a0793\"\n ]\n },\n \"total_experience_months\": {\n \"gte\": 1,\n \"lte\": 20\n },\n \"country_code\": {\n \"values\": [\n \"us\",\n \"dk\",\n \"uk\"\n ]\n },\n \"region_country_code\": {\n \"values\": [\n \"us-ca\",\n \"us-ny\"\n ]\n },\n \"current_role_months\": {\n \"gte\": 1,\n \"lte\": 200\n },\n \"company_size\": {\n \"values\": [\n \"5001-10000\"\n ]\n },\n \"company_revenue\": {\n \"values\": [\n \"500K-1M\",\n \"5M-10M\"\n ]\n },\n \"google_category\": {\n \"values\": [\n \"construction\"\n ]\n },\n \"naics_category\": {\n \"values\": [\n \"541512\"\n ]\n },\n \"linkedin_category\": {\n \"values\": [\n \"retail\",\n \"software development\"\n ]\n },\n \"job_title\": {\n \"values\": [\n \"Software Engineer\"\n ]\n },\n \"company_country_code\": {\n \"values\": [\n \"us\",\n \"jp\"\n ]\n },\n \"company_region_country_code\": {\n \"values\": [\n \"us-ca\"\n ]\n },\n \"city_region_country\": {\n \"values\": [\n \"Paris, FR\"\n ]\n },\n \"company_name\": {\n \"values\": [\n \"Microsoft\"\n ]\n }\n }\n}" }, "typeVersion": 1.2 }, { "id": "e1e4391e-f199-4669-b62f-1d2f3f93aeff", "name": "Explorium Prospects API Call", "type": "n8n-nodes-base.httpRequest", "position": [ 0, 0 ], "parameters": { "url": "=https://api.explorium.ai/v1/prospects", "method": "POST", "options": { "pagination": { "pagination": { "parameters": { "parameters": [ { "name": "page", "type": "body", "value": "={{$response.body.page + 1}}" } ] }, "limitPagesFetched": true, "completeExpression": "={{$response.body.data.length === 0}}", "paginationCompleteWhen": "other" } } }, "jsonBody": "={{ $json.output }}", "sendBody": true, "sendHeaders": true, "specifyBody": "json", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" } ] } }, "credentials": { "httpHeaderAuth": { "id": "85mkGmNNdK1951hF", "name": "Header Auth Connection" } }, "typeVersion": 4.2 } ], "active": false, "pinData": {}, "settings": { "executionOrder": "v1" }, "versionId": "", "connections": { "AI Agent": { "main": [ [ { "node": "API Call Validation", "type": "main", "index": 0 } ] ] }, "Explorium MCP": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }, "Output Parser": { "ai_outputParser": [ [ { "node": "AI Agent", "type": "ai_outputParser", "index": 0 } ] ] }, "Simple Memory": { "ai_memory": [ [ { "node": "AI Agent", "type": "ai_memory", "index": 0 } ] ] }, "Extract \"data\"": { "main": [ [ { "node": "Prepare for CSV", "type": "main", "index": 0 } ] ] }, "Merge All Pages": { "main": [ [ { "node": "Extract \"data\"", "type": "main", "index": 0 } ] ] }, "Prepare for CSV": { "main": [ [ { "node": "Convert to File", "type": "main", "index": 0 } ] ] }, "Chat or Refinement": { "main": [ [ { "node": "AI Agent", "type": "main", "index": 0 } ] ] }, "Is API Call Valid?": { "main": [ [ { "node": "Explorium Prospects API Call", "type": "main", "index": 0 } ], [ { "node": "Validation Prompter", "type": "main", "index": 0 } ] ] }, "API Call Validation": { "main": [ [ { "node": "Is API Call Valid?", "type": "main", "index": 0 } ] ] }, "Validation Prompter": { "main": [ [ { "node": "Chat or Refinement", "type": "main", "index": 0 } ] ] }, "Anthropic Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 } ] ] }, "When chat message received": { "main": [ [ { "node": "Chat or Refinement", "type": "main", "index": 0 } ] ] }, "Explorium Prospects API Call": { "main": [ [ { "node": "Merge All Pages", "type": "main", "index": 0 } ] ] } } }