{
  "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"
    }
  ]
}