This workflow follows the Execute Workflow Trigger → Postgres 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": "2673a82a-e155-4d0c-85b4-92922b74849d",
"name": "SUB-001-provider-gateway",
"nodes": [
{
"parameters": {
"inputSource": "passthrough"
},
"id": "b24fa15e-f5e5-4769-9bd0-dd29cad3e331",
"name": "Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
240,
500
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT prompt_prefix FROM brand_profiles WHERE channel_id = 1 LIMIT 1",
"options": {}
},
"id": "7bc11293-6ab8-4635-b0b0-b4fdbeedc579",
"name": "Load Brand Prefix",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
460,
500
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1b2c3d4-0001-0001-0001-000000000001",
"name": "system_prompt",
"value": "={{ $('Trigger').item.json.system_prompt ? ($json.prompt_prefix + '\\n\\n' + $('Trigger').item.json.system_prompt) : '' }}",
"type": "string"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000002",
"name": "task_type",
"value": "={{ $('Trigger').item.json.task_type }}",
"type": "string"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000003",
"name": "user_prompt",
"value": "={{ $('Trigger').item.json.user_prompt }}",
"type": "string"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000004",
"name": "output_format",
"value": "={{ $('Trigger').item.json.output_format || 'text' }}",
"type": "string"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000005",
"name": "max_tokens",
"value": "={{ $('Trigger').item.json.max_tokens || 2000 }}",
"type": "number"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000006",
"name": "temperature",
"value": "={{ $('Trigger').item.json.temperature ?? 0.7 }}",
"type": "number"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000007",
"name": "workflow_name",
"value": "={{ $('Trigger').item.json.workflow_name || 'UNKNOWN' }}",
"type": "string"
},
{
"id": "a1b2c3d4-0001-0001-0001-000000000008",
"name": "asset_id",
"value": "={{ $('Trigger').item.json.asset_id || null }}",
"type": "string"
}
]
},
"options": {}
},
"id": "28439f74-e871-4ec2-9052-339136f77e2f",
"name": "Inject Brand Prefix",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
680,
500
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ \"SELECT id, provider_name, base_url, api_key_env, model_name, cost_per_m_in::float8, cost_per_m_out::float8, priority FROM providers WHERE enabled = true AND (health = 'healthy' OR (health = 'degraded' AND updated_at < NOW() - INTERVAL '15 minutes')) AND '\" + $json.task_type + \"' = ANY(task_types) AND (rpm_limit IS NULL OR rpm_used < rpm_limit) AND (daily_limit IS NULL OR daily_used < daily_limit) ORDER BY priority ASC\" }}",
"options": {}
},
"id": "da3753af-5e92-42c2-8c12-0e38fb832d17",
"name": "Query Available Providers",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
900,
500
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Aggregate provider rows into single item with providers array\nconst providers = $input.all().map(i => i.json).filter(p => p.id != null);\nconst req = $('Inject Brand Prefix').item.json;\n\nreturn [{\n json: {\n providers_found: providers.length > 0,\n providers: providers,\n // carry request data forward\n task_type: req.task_type,\n system_prompt: req.system_prompt,\n user_prompt: req.user_prompt,\n output_format: req.output_format,\n max_tokens: req.max_tokens,\n temperature: req.temperature,\n workflow_name: req.workflow_name,\n asset_id: req.asset_id\n }\n}];"
},
"id": "8d7b0744-b6d0-4b91-8609-c5a1993141e8",
"name": "Check Providers",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
500
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-providers-found",
"leftValue": "={{ $json.providers_found }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "61706dba-c184-48aa-859a-ab2c6e9aaeed",
"name": "IF Providers Available",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1340,
500
]
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const item = $input.first().json;\nconst providers = item.providers;\nconst req = {\n task_type: item.task_type,\n system_prompt: item.system_prompt,\n user_prompt: item.user_prompt,\n output_format: item.output_format,\n max_tokens: item.max_tokens || 2000,\n temperature: item.temperature ?? 0.7,\n workflow_name: item.workflow_name,\n asset_id: item.asset_id\n};\n\nconst providersTried = [];\nconst failedProviders = [];\nlet result = null;\n\nfor (const provider of providers) {\n providersTried.push(provider.provider_name);\n\n const ENV_KEYS = {\n GROQ_API_KEY: $env.GROQ_API_KEY,\n GEMINI_API_KEY: $env.GEMINI_API_KEY,\n OPENROUTER_API_KEY: $env.OPENROUTER_API_KEY\n};\nconst apiKey = ENV_KEYS[provider.api_key_env];\n if (!apiKey) {\n failedProviders.push({\n provider_id: provider.id,\n provider_name: provider.provider_name,\n error_type: 'client_error',\n error: `Missing env var: ${provider.api_key_env}`\n });\n continue;\n }\n\n const callStart = Date.now();\n let responseStatus, responseBody;\n\n const httpsPost = (url, headers, body) => new Promise((resolve, reject) => {\n const https = require('https');\n // Manual URL parse \u2014 URL global not available in n8n sandbox\n const noProto = url.replace(/^https?:\\/\\//, '');\n const slashIdx = noProto.indexOf('/');\n const hostname = slashIdx === -1 ? noProto : noProto.slice(0, slashIdx);\n const path = slashIdx === -1 ? '/' : noProto.slice(slashIdx);\n const payload = JSON.stringify(body);\n const req = https.request({\n hostname,\n path,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload), ...headers }\n }, (res) => {\n let data = '';\n res.on('data', c => data += c);\n res.on('end', () => { try { resolve({ status: res.statusCode, body: JSON.parse(data) }); } catch(e) { resolve({ status: res.statusCode, body: data }); } });\n });\n req.on('error', reject);\n req.setTimeout(30000, () => { req.destroy(new Error('Request timeout')); });\n req.write(payload);\n req.end();\n });\n\n try {\n if (provider.provider_name === 'gemini') {\n const geminiBody = {\n system_instruction: { parts: [{ text: req.system_prompt }] },\n contents: [{ parts: [{ text: req.user_prompt }] }],\n generationConfig: {\n maxOutputTokens: req.max_tokens,\n temperature: req.temperature,\n ...(req.output_format === 'json' ? { responseMimeType: 'application/json' } : {})\n }\n };\n const r = await httpsPost(\n `${provider.base_url}/v1beta/models/${provider.model_name}:generateContent?key=${apiKey}`,\n {},\n geminiBody\n );\n responseStatus = r.status;\n responseBody = r.body;\n } else {\n const oaiBody = {\n model: provider.model_name,\n messages: [\n { role: 'system', content: req.system_prompt },\n { role: 'user', content: req.user_prompt }\n ],\n max_tokens: req.max_tokens,\n temperature: req.temperature,\n ...(req.output_format === 'json' ? { response_format: { type: 'json_object' } } : {})\n };\n const r = await httpsPost(\n `${provider.base_url}/chat/completions`,\n { 'Authorization': `Bearer ${apiKey}` },\n oaiBody\n );\n responseStatus = r.status;\n responseBody = r.body;\n }\n } catch (e) {\n failedProviders.push({\n provider_id: provider.id,\n provider_name: provider.provider_name,\n error_type: 'timeout',\n error: e.message\n });\n continue;\n }\n\n const latencyMs = Date.now() - callStart;\n\n if (responseStatus === 429) {\n failedProviders.push({ provider_id: provider.id, provider_name: provider.provider_name, error_type: 'rate_limited' });\n continue;\n }\n if (responseStatus >= 500) {\n failedProviders.push({ provider_id: provider.id, provider_name: provider.provider_name, error_type: 'server_error', error: JSON.stringify(responseBody) });\n continue;\n }\n if (responseStatus >= 400) {\n failedProviders.push({ provider_id: provider.id, provider_name: provider.provider_name, error_type: 'client_error', error: JSON.stringify(responseBody) });\n continue;\n }\n\n // Parse content\n let content, inputTokens, outputTokens;\n try {\n if (provider.provider_name === 'gemini') {\n content = responseBody.candidates[0].content.parts[0].text;\n inputTokens = responseBody.usageMetadata?.promptTokenCount || 0;\n outputTokens = responseBody.usageMetadata?.candidatesTokenCount || 0;\n } else {\n content = responseBody.choices[0].message.content;\n inputTokens = responseBody.usage?.prompt_tokens || 0;\n outputTokens = responseBody.usage?.completion_tokens || 0;\n }\n } catch (e) {\n failedProviders.push({ provider_id: provider.id, provider_name: provider.provider_name, error_type: 'client_error', error: `Parse failed: ${e.message}` });\n continue;\n }\n\n const costUsd =\n (inputTokens * Number(provider.cost_per_m_in) / 1000000) +\n (outputTokens * Number(provider.cost_per_m_out) / 1000000);\n\n let responseJson = {};\n let jsonParseFailed = false;\n if (req.output_format === 'json') {\n try { responseJson = JSON.parse(content); }\n catch (e) { jsonParseFailed = true; }\n }\n\n // Build health update SQL for any providers that failed before this success\n let healthUpdateSql = '';\n if (failedProviders.length > 0) {\n const rateLimitedIds = failedProviders.filter(p => p.error_type === 'rate_limited').map(p => p.provider_id);\n const serverErrorIds = failedProviders.filter(p => p.error_type === 'server_error' || p.error_type === 'timeout').map(p => p.provider_id);\n const sqlParts = [];\n if (rateLimitedIds.length > 0) {\n sqlParts.push(`UPDATE providers SET health = 'degraded', updated_at = NOW() WHERE id IN (${rateLimitedIds.join(',')})`);\n }\n if (serverErrorIds.length > 0) {\n sqlParts.push(`UPDATE providers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id IN (${serverErrorIds.join(',')})`);\n sqlParts.push(`UPDATE providers SET health = 'down', updated_at = NOW() WHERE failure_count >= 3 AND id IN (${serverErrorIds.join(',')})`);\n }\n healthUpdateSql = sqlParts.join('; ');\n }\n\n result = {\n success: true,\n provider_id: provider.id,\n provider_name: provider.provider_name,\n model_name: provider.model_name,\n response_text: content,\n response_json: responseJson,\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n cost_usd: costUsd,\n latency_ms: latencyMs,\n providers_tried: providersTried,\n failed_providers: failedProviders,\n json_parse_failed: jsonParseFailed,\n health_update_sql: healthUpdateSql,\n task_type: req.task_type,\n workflow_name: req.workflow_name,\n asset_id: req.asset_id\n };\n break;\n}\n\nif (!result) {\n // Build health update SQL for all failed providers\n let healthUpdateSql = '';\n if (failedProviders.length > 0) {\n const rateLimitedIds = failedProviders.filter(p => p.error_type === 'rate_limited').map(p => p.provider_id);\n const serverErrorIds = failedProviders.filter(p => p.error_type === 'server_error' || p.error_type === 'timeout').map(p => p.provider_id);\n const sqlParts = [];\n if (rateLimitedIds.length > 0) {\n sqlParts.push(`UPDATE providers SET health = 'degraded', updated_at = NOW() WHERE id IN (${rateLimitedIds.join(',')})`);\n }\n if (serverErrorIds.length > 0) {\n sqlParts.push(`UPDATE providers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id IN (${serverErrorIds.join(',')})`);\n sqlParts.push(`UPDATE providers SET health = 'down', updated_at = NOW() WHERE failure_count >= 3 AND id IN (${serverErrorIds.join(',')})`);\n }\n healthUpdateSql = sqlParts.join('; ');\n }\n\n return [{\n json: {\n success: false,\n error: `All providers unavailable for task_type: ${req.task_type}`,\n providers_tried: providersTried,\n failed_providers: failedProviders,\n health_update_sql: healthUpdateSql,\n task_type: req.task_type,\n workflow_name: req.workflow_name,\n asset_id: req.asset_id\n }\n }];\n}\n\nreturn [{ json: result }];"
},
"id": "3e733a9e-bbd5-455f-9a23-b64f41043769",
"name": "Provider Loop",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
380
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-success",
"leftValue": "={{ $json.success }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "f56266e3-bf25-4bc4-8866-fab4b2690d67",
"name": "IF Success",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1780,
380
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `UPDATE providers SET rpm_used = rpm_used + 1, daily_used = daily_used + 1, avg_latency_ms = ((avg_latency_ms + ${$json.latency_ms}) / 2)::integer, failure_count = 0, health = 'healthy', updated_at = NOW() WHERE id = ${$json.provider_id}` }}",
"options": {}
},
"id": "96073300-c04e-4ae6-91dd-72b341012258",
"name": "Update Provider Stats",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2000,
260
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `INSERT INTO provider_usage_logs (provider_id, workflow_name, task_type, asset_id, input_tokens, output_tokens, cost_usd, latency_ms, status) VALUES (${$('Provider Loop').item.json.provider_id}, '${$('Provider Loop').item.json.workflow_name}', '${$('Provider Loop').item.json.task_type}', ${$('Provider Loop').item.json.asset_id || 'NULL'}, ${$('Provider Loop').item.json.input_tokens}, ${$('Provider Loop').item.json.output_tokens}, ${$('Provider Loop').item.json.cost_usd}, ${$('Provider Loop').item.json.latency_ms}, 'success') RETURNING id` }}",
"options": {}
},
"id": "282c17c0-8d00-4de3-a156-602fc6d2ab61",
"name": "Log API Call",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2220,
260
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "out-success-001",
"name": "success",
"value": true,
"type": "boolean"
},
{
"id": "out-success-002",
"name": "provider_used",
"value": "={{ $('Provider Loop').item.json.provider_name }}",
"type": "string"
},
{
"id": "out-success-003",
"name": "model_used",
"value": "={{ $('Provider Loop').item.json.model_name }}",
"type": "string"
},
{
"id": "out-success-004",
"name": "response_text",
"value": "={{ $('Provider Loop').item.json.response_text }}",
"type": "string"
},
{
"id": "out-success-005",
"name": "response_json",
"value": "={{ $('Provider Loop').item.json.response_json }}",
"type": "object"
},
{
"id": "out-success-006",
"name": "input_tokens",
"value": "={{ $('Provider Loop').item.json.input_tokens }}",
"type": "number"
},
{
"id": "out-success-007",
"name": "output_tokens",
"value": "={{ $('Provider Loop').item.json.output_tokens }}",
"type": "number"
},
{
"id": "out-success-008",
"name": "cost_usd",
"value": "={{ $('Provider Loop').item.json.cost_usd }}",
"type": "number"
},
{
"id": "out-success-009",
"name": "latency_ms",
"value": "={{ $('Provider Loop').item.json.latency_ms }}",
"type": "number"
}
]
},
"options": {}
},
"id": "83bee69b-6236-46a2-9367-04319eae5826",
"name": "Return Success",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2440,
260
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ $json.health_update_sql && $json.health_update_sql.length > 0 ? $json.health_update_sql : 'SELECT 1' }}",
"options": {}
},
"id": "84e2be62-9b28-4423-be93-7ada715a2d61",
"name": "Update Provider Health",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2000,
500
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const req = $('Inject Brand Prefix').item.json;\n\n// Try to get Provider Loop failure data (not available on no-providers path)\nlet loopData = {};\ntry { loopData = $('Provider Loop').item.json; } catch(e) {}\n\nconst d = Object.keys(loopData).length > 0 ? loopData : $input.first().json;\n\nreturn [{\n json: {\n success: false,\n workflow_name: d.workflow_name || req.workflow_name,\n task_type: d.task_type || req.task_type,\n asset_id: d.asset_id !== undefined ? d.asset_id : req.asset_id,\n error: d.error || 'No healthy providers available for task_type: ' + req.task_type,\n providers_tried: d.providers_tried || [],\n failed_providers: d.failed_providers || []\n }\n}];"
},
"id": "548bcb8d-f3d3-4628-a9a2-409f2764055b",
"name": "Format Failure",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2220,
500
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ `INSERT INTO errors (workflow_name, error_type, error_message, payload) VALUES ('${$json.workflow_name}', 'provider_exhausted', '${$json.error.replace(/'/g, \"''\")}', '${JSON.stringify({providers_tried: $json.providers_tried, task_type: $json.task_type})}') RETURNING id` }}",
"options": {}
},
"id": "3a2a2c4b-cf85-41a4-9215-82175c8e1274",
"name": "Write Error",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2440,
500
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "out-fail-001",
"name": "success",
"value": false,
"type": "boolean"
},
{
"id": "out-fail-002",
"name": "error",
"value": "={{ $('Format Failure').item.json.error }}",
"type": "string"
},
{
"id": "out-fail-003",
"name": "providers_tried",
"value": "={{ $('Format Failure').item.json.providers_tried }}",
"type": "array"
},
{
"id": "out-fail-004",
"name": "workflow_name",
"value": "={{ $('Format Failure').item.json.workflow_name }}",
"type": "string"
}
]
},
"options": {}
},
"id": "31c5228a-276a-4b69-a956-81274e742f9f",
"name": "Return Failure",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2660,
500
]
}
],
"connections": {
"Trigger": {
"main": [
[
{
"node": "Load Brand Prefix",
"type": "main",
"index": 0
}
]
]
},
"Load Brand Prefix": {
"main": [
[
{
"node": "Inject Brand Prefix",
"type": "main",
"index": 0
}
]
]
},
"Inject Brand Prefix": {
"main": [
[
{
"node": "Query Available Providers",
"type": "main",
"index": 0
}
]
]
},
"Query Available Providers": {
"main": [
[
{
"node": "Check Providers",
"type": "main",
"index": 0
}
]
]
},
"Check Providers": {
"main": [
[
{
"node": "IF Providers Available",
"type": "main",
"index": 0
}
]
]
},
"IF Providers Available": {
"main": [
[
{
"node": "Provider Loop",
"type": "main",
"index": 0
}
],
[
{
"node": "Format Failure",
"type": "main",
"index": 0
}
]
]
},
"Provider Loop": {
"main": [
[
{
"node": "IF Success",
"type": "main",
"index": 0
}
]
]
},
"IF Success": {
"main": [
[
{
"node": "Update Provider Stats",
"type": "main",
"index": 0
}
],
[
{
"node": "Update Provider Health",
"type": "main",
"index": 0
}
]
]
},
"Update Provider Stats": {
"main": [
[
{
"node": "Log API Call",
"type": "main",
"index": 0
}
]
]
},
"Log API Call": {
"main": [
[
{
"node": "Return Success",
"type": "main",
"index": 0
}
]
]
},
"Update Provider Health": {
"main": [
[
{
"node": "Format Failure",
"type": "main",
"index": 0
}
]
]
},
"Format Failure": {
"main": [
[
{
"node": "Write Error",
"type": "main",
"index": 0
}
]
]
},
"Write Error": {
"main": [
[
{
"node": "Return Failure",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner"
},
"staticData": null,
"meta": {
"templateCredsSetupCompleted": false
},
"tags": [
{
"name": "subworkflow"
},
{
"name": "provider-gateway"
}
]
}
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.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
SUB-001-provider-gateway. Uses executeWorkflowTrigger, postgres. Event-driven trigger; 15 nodes.
Source: https://github.com/navin-labs/content-factory-os/blob/main/n8n/workflows/subworkflows/SUB-001-provider-gateway.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.
Reagendamiento_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 89 nodes.
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.
Save_Extraction. Uses executeWorkflowTrigger, postgres, httpRequest. Event-driven trigger; 22 nodes.
Youtube Searcher. Uses splitInBatches, httpRequest, manualTrigger, executeWorkflowTrigger. Event-driven trigger; 21 nodes.