AutomationFlowsGeneral › Getopenapi

Getopenapi

getOpenAPI. Uses n8n. Webhook trigger; 5 nodes.

Webhook trigger★★★★☆ complexity5 nodesn8n
General Trigger: Webhook Nodes: 5 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #4270 — we link there as the canonical source.

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 →

Download .json
{
  "name": "getOpenAPI",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "filters": {
          "activeWorkflows": true
        },
        "requestOptions": {}
      },
      "id": "1f397969-aa2e-435b-b0e9-9ef66face5fc",
      "name": "n8n",
      "type": "n8n-nodes-base.n8n",
      "position": [
        592,
        512
      ],
      "typeVersion": 1,
      "credentials": {
        "n8nApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "function safe(x = \"\") {\n  return String(x).replaceAll(\":\", \"\").replaceAll(\"/\", \"|\");\n}\n\n/**\n * YAML SERIALIZER (safe)\n */\nfunction toYAML(obj, indent = 0) {\n  const spaces = \"  \".repeat(indent);\n\n  if (obj === null || obj === undefined) return \"\";\n  if (typeof obj === \"string\") {\n    if (obj.includes(\":\") || obj.includes(\"#\") || obj.includes(\"\\n\")) {\n      return `\"${obj.replace(/\"/g, '\\\\\"')}\"`;\n    }\n    return obj;\n  }\n  if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n    return String(obj);\n  }\n\n  if (Array.isArray(obj)) {\n    if (!obj.length) return \"[]\";\n    return obj\n      .map(item => `${spaces}- ${isPrimitive(item) ? toYAML(item, indent + 1) : \"\\n\" + toYAML(item, indent + 1)}`)\n      .join(\"\\n\");\n  }\n\n  if (typeof obj === \"object\") {\n    const entries = Object.entries(obj).filter(([_, v]) => v !== undefined);\n    if (!entries.length) return \"{}\";\n\n    return entries\n      .map(([key, value]) => {\n        if (isPrimitive(value)) {\n          return `${spaces}${key}: ${toYAML(value, indent + 1)}`;\n        }\n        return `${spaces}${key}:\\n${toYAML(value, indent + 1)}`;\n      })\n      .join(\"\\n\");\n  }\n\n  return \"\";\n}\n\nfunction isPrimitive(val) {\n  return (\n    val === null ||\n    typeof val === \"string\" ||\n    typeof val === \"number\" ||\n    typeof val === \"boolean\"\n  );\n}\n\n/**\n * DFS \u2014 Safe traversal\n */\nfunction findValidTargets(connections = {}, sourceNode, potentialTargets = []) {\n  if (!sourceNode) return [];\n\n  const visited = new Set();\n  const foundTargets = new Set();\n\n  function dfs(node) {\n    if (!node || visited.has(node)) return;\n    visited.add(node);\n\n    if (potentialTargets.includes(node)) {\n      foundTargets.add(node);\n    }\n\n    const nodeConnections = connections[node]?.main || [];\n    for (const path of nodeConnections) {\n      for (const next of path || []) {\n        dfs(next?.node);\n      }\n    }\n  }\n\n  dfs(sourceNode);\n  return Array.from(foundTargets);\n}\n\n/**\n * Robust Annotation Parser\n */\nfunction parseAnnotations(notes) {\n  if (!notes || typeof notes !== \"string\") {\n    return emptyParseResult();\n  }\n\n  const lines = notes\n    .split(\"\\n\")\n    .map(l => l.trim())\n    .filter(l => l.startsWith(\"@\"));\n\n  const parameters = [];\n  const responses = {};\n  const requestBodySchema = {\n    type: \"object\",\n    properties: {},\n    required: []\n  };\n\n  let hasExplicitResponse = false;\n\n  function setNestedProperty(schema, pathParts, type, required) {\n    if (!pathParts.length) return;\n\n    let current = schema;\n\n    for (let i = 0; i < pathParts.length; i++) {\n      const part = pathParts[i];\n      if (!part) return;\n\n      if (!current.properties[part]) {\n        current.properties[part] = { type: \"object\", properties: {}, required: [] };\n      }\n\n      if (i === pathParts.length - 1) {\n        current.properties[part] = { type: type || \"string\" };\n        if (required && !current.required.includes(part)) {\n          current.required.push(part);\n        }\n      } else {\n        current = current.properties[part];\n      }\n    }\n  }\n\n  for (const line of lines) {\n    try {\n      const parts = line.split(/\\s+/);\n      const directive = parts[0];\n\n      if (!directive) continue;\n\n      /**\n       * PARAMETERS\n       */\n      if ([\"@query\", \"@path\", \"@header\"].includes(directive)) {\n        if (parts.length < 2) continue;\n\n        const name = parts[1];\n        const type = parts[2] || \"string\";\n        const maybeRequired = parts[3];\n        const description =\n          parts.length > 3\n            ? parts.slice(\n                maybeRequired === \"required\" || maybeRequired === \"optional\" ? 4 : 3\n              ).join(\" \")\n            : \"No description\";\n\n        let required = true; // legacy default\n\n        if (maybeRequired === \"required\" || maybeRequired === \"optional\") {\n          required = maybeRequired === \"required\";\n        }\n\n        if (directive === \"@path\") required = true;\n\n        parameters.push({\n          name,\n          in: directive.replace(\"@\", \"\"),\n          required,\n          description: description || \"No description\",\n          schema: { type }\n        });\n      }\n\n      /**\n       * BODY\n       */\n      if (directive === \"@body\") {\n        if (parts.length < 2) continue;\n\n        const name = parts[1];\n        const type = parts[2] || \"string\";\n        const maybeRequired = parts[3];\n\n        const required = maybeRequired === \"optional\" ? false : true;\n\n        const pathParts = name.split(\".\");\n        setNestedProperty(requestBodySchema, pathParts, type, required);\n      }\n\n      /**\n       * RESPONSE\n       */\n      if (directive === \"@response\") {\n        if (parts.length < 2) continue;\n\n        hasExplicitResponse = true;\n\n        const code = parts[1] || \"200\";\n        const contentType = parts[2] || \"application/json\";\n        const description = parts.slice(3).join(\" \") || \"Response\";\n\n        responses[code] = {\n          description,\n          content: contentType === \"redirect\"\n            ? undefined\n            : {\n                [contentType]: {\n                  schema: { type: \"object\" }\n                }\n              }\n        };\n      }\n\n    } catch (err) {\n      // Ignore malformed lines silently\n      continue;\n    }\n  }\n\n  const hasBodyProps = Object.keys(requestBodySchema.properties).length > 0;\n\n  return {\n    parameters,\n    requestBody: hasBodyProps\n      ? {\n          content: {\n            \"application/json\": {\n              schema: cleanSchema(requestBodySchema)\n            }\n          }\n        }\n      : undefined,\n    responses,\n    hasExplicitResponse\n  };\n}\n\nfunction cleanSchema(schema) {\n  if (!schema.required || !schema.required.length) {\n    delete schema.required;\n  }\n  return schema;\n}\n\nfunction emptyParseResult() {\n  return {\n    parameters: [],\n    requestBody: undefined,\n    responses: {},\n    hasExplicitResponse: false\n  };\n}\n\n/**\n * Infer legacy response safely\n */\nfunction inferResponsesFromNodes(webhook) {\n  let produces = \"application/json\";\n  let code = \"200\";\n\n  if (Array.isArray(webhook?.responses)) {\n    for (const r of webhook.responses) {\n      switch (r?.parameters?.respondWith) {\n        case \"text\":\n          produces = \"text/plain\";\n          break;\n        case \"redirect\":\n          produces = \"text/plain\";\n          code = \"301\";\n          break;\n        case \"json\":\n          produces = \"application/json\";\n          break;\n      }\n    }\n  }\n\n  return {\n    [code]: {\n      description: \"Successful response\",\n      content: {\n        [produces]: {\n          schema: { type: \"object\" }\n        }\n      }\n    }\n  };\n}\n\n/**\n * BUILD OPENAPI DOCUMENT\n */\nconst openapi = {\n  openapi: \"3.0.3\",\n  info: {\n    title: \"N8N Instance API\",\n    version: \"1.0.0\",\n    description: \"Auto-generated OpenAPI spec from n8n workflows\"\n  },\n  servers: [\n    {\n      url: `https://${$('Get Swagger').first().json?.headers?.host || \"n8n.instance.com\"}/webhook`\n    }\n  ],\n  paths: {}\n};\n\nfor (const item of $input.all()) {\n  const nodes = item.json?.nodes || [];\n  const connections = item.json?.connections || {};\n\n  const webhooks = nodes.filter(n => n?.type === \"n8n-nodes-base.webhook\");\n  const responseNodes = nodes.filter(n => n?.type === \"n8n-nodes-base.respondToWebhook\");\n  const targets = responseNodes.map(r => r.name);\n\n  for (const w of webhooks) {\n    try {\n      if (w?.parameters?.responseMode === \"responseNode\") {\n        const valid = findValidTargets(connections, w.name, targets);\n        w.responses = responseNodes.filter(r => valid.includes(r.name));\n      }\n\n      const path = `/${w?.parameters?.path || \"\"}`;\n      const method = (w?.parameters?.httpMethod || \"get\").toLowerCase();\n\n      if (!openapi.paths[path]) openapi.paths[path] = {};\n\n      const { parameters, requestBody, responses, hasExplicitResponse } =\n        parseAnnotations(w?.notes);\n\n      const finalResponses = hasExplicitResponse\n        ? responses\n        : inferResponsesFromNodes(w);\n\n      openapi.paths[path][method] = {\n        summary: safe(w?.name || \"Webhook\"),\n        description: `Related to workflow [${item.json?.id || \"unknown\"}]`,\n        tags: [safe(item.json?.name || \"Workflow\")],\n        parameters: parameters.length ? parameters : undefined,\n        requestBody,\n        responses: Object.keys(finalResponses).length\n          ? finalResponses\n          : inferResponsesFromNodes(w)\n      };\n    } catch (err) {\n      // Skip malformed webhook safely\n      continue;\n    }\n  }\n}\n\nconst yamlOutput = toYAML(openapi);\n\nreturn {\n  json: {\n    yamlOutput\n  }\n};"
      },
      "id": "37b03898-8b7b-4511-8a54-b6777118ea76",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        512
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html>\n<html>\n<head>\n  <title>Swagger UI from YAML Text</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist/swagger-ui.css\">\n</head>\n<body>\n  <div id=\"swagger-ui\"></div>\n\n  <script src=\"https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js\"></script>\n  <script src=\"https://unpkg.com/js-yaml@4.1.0/dist/js-yaml.min.js\"></script>\n  <script>\n    // Your YAML as a string (replace this with your actual variable)\n    const yamlText = `{{ $json.yamlOutput }}`;\n\n    // Parse the YAML into a JavaScript object\n    const spec = jsyaml.load(yamlText);\n\n    // Initialize Swagger UI with the parsed spec\n    const ui = SwaggerUIBundle({\n      spec: spec,\n      dom_id: \"#swagger-ui\"\n    });\n  </script>\n</body>\n</html>\n",
        "options": {}
      },
      "id": "872dbe8f-a4e2-4828-98d5-73f06ae116ad",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1040,
        512
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "path": "swagger",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "7868c867-85c4-422d-a9f3-7424af49fd14",
      "name": "Get Swagger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        368,
        512
      ],
      "typeVersion": 2,
      "notes": "//@body field_name string description"
    },
    {
      "parameters": {
        "content": "## Configure webhooks\n\n### WebhookDocs: generate swagger preview of your active workflows\n\nIn order to support parameter labels you have to open the note sections of every webhook and add the following text\n\n//@body field_name string description\n//@query field_name string description\n\nAdapted from https://n8n.io/workflows/4270-webhookdocs-generate-swagger-preview-of-your-active-workflows/\n",
        "height": 244,
        "width": 480
      },
      "id": "30aa0403-c828-48eb-862b-4b258249c1e1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        208
      ],
      "typeVersion": 1
    }
  ],
  "connections": {
    "n8n": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Swagger": {
      "main": [
        [
          {
            "node": "n8n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "staticData": null,
  "meta": {
    "templateId": "4270",
    "templateCredsSetupCompleted": true
  },
  "versionId": "01ce8d13-8949-4854-b8e8-7b8e0340d47b",
  "activeVersionId": "01ce8d13-8949-4854-b8e8-7b8e0340d47b",
  "versionCounter": 112,
  "triggerCount": 1,
  "shared": [
    {
      "updatedAt": "2026-03-06T22:19:19.113Z",
      "createdAt": "2026-03-06T22:19:19.113Z",
      "role": "workflow:owner",
      "workflowId": "NsXpM2VX76Okkh43",
      "projectId": "rbmFyQ0fPfUjlaVB",
      "project": {
        "updatedAt": "2025-10-28T16:01:24.976Z",
        "createdAt": "2025-10-28T15:59:42.193Z",
        "id": "rbmFyQ0fPfUjlaVB",
        "name": "sean keery <sean.keery@rmscorp.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "0bf0b226-8ea5-4163-8cc1-8ca9663cae74"
      }
    }
  ],
  "tags": [],
  "activeVersion": {
    "updatedAt": "2026-03-16T19:02:54.326Z",
    "createdAt": "2026-03-16T19:02:42.909Z",
    "versionId": "01ce8d13-8949-4854-b8e8-7b8e0340d47b",
    "workflowId": "NsXpM2VX76Okkh43",
    "nodes": [
      {
        "parameters": {
          "filters": {
            "activeWorkflows": true
          },
          "requestOptions": {}
        },
        "id": "1f397969-aa2e-435b-b0e9-9ef66face5fc",
        "name": "n8n",
        "type": "n8n-nodes-base.n8n",
        "position": [
          592,
          512
        ],
        "typeVersion": 1,
        "credentials": {
          "n8nApi": {
            "id": "Hmm3fIMtxCqRshgA",
            "name": "Azure n8n dev account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "function safe(x = \"\") {\n  return String(x).replaceAll(\":\", \"\").replaceAll(\"/\", \"|\");\n}\n\n/**\n * YAML SERIALIZER (safe)\n */\nfunction toYAML(obj, indent = 0) {\n  const spaces = \"  \".repeat(indent);\n\n  if (obj === null || obj === undefined) return \"\";\n  if (typeof obj === \"string\") {\n    if (obj.includes(\":\") || obj.includes(\"#\") || obj.includes(\"\\n\")) {\n      return `\"${obj.replace(/\"/g, '\\\\\"')}\"`;\n    }\n    return obj;\n  }\n  if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n    return String(obj);\n  }\n\n  if (Array.isArray(obj)) {\n    if (!obj.length) return \"[]\";\n    return obj\n      .map(item => `${spaces}- ${isPrimitive(item) ? toYAML(item, indent + 1) : \"\\n\" + toYAML(item, indent + 1)}`)\n      .join(\"\\n\");\n  }\n\n  if (typeof obj === \"object\") {\n    const entries = Object.entries(obj).filter(([_, v]) => v !== undefined);\n    if (!entries.length) return \"{}\";\n\n    return entries\n      .map(([key, value]) => {\n        if (isPrimitive(value)) {\n          return `${spaces}${key}: ${toYAML(value, indent + 1)}`;\n        }\n        return `${spaces}${key}:\\n${toYAML(value, indent + 1)}`;\n      })\n      .join(\"\\n\");\n  }\n\n  return \"\";\n}\n\nfunction isPrimitive(val) {\n  return (\n    val === null ||\n    typeof val === \"string\" ||\n    typeof val === \"number\" ||\n    typeof val === \"boolean\"\n  );\n}\n\n/**\n * DFS \u2014 Safe traversal\n */\nfunction findValidTargets(connections = {}, sourceNode, potentialTargets = []) {\n  if (!sourceNode) return [];\n\n  const visited = new Set();\n  const foundTargets = new Set();\n\n  function dfs(node) {\n    if (!node || visited.has(node)) return;\n    visited.add(node);\n\n    if (potentialTargets.includes(node)) {\n      foundTargets.add(node);\n    }\n\n    const nodeConnections = connections[node]?.main || [];\n    for (const path of nodeConnections) {\n      for (const next of path || []) {\n        dfs(next?.node);\n      }\n    }\n  }\n\n  dfs(sourceNode);\n  return Array.from(foundTargets);\n}\n\n/**\n * Robust Annotation Parser\n */\nfunction parseAnnotations(notes) {\n  if (!notes || typeof notes !== \"string\") {\n    return emptyParseResult();\n  }\n\n  const lines = notes\n    .split(\"\\n\")\n    .map(l => l.trim())\n    .filter(l => l.startsWith(\"@\"));\n\n  const parameters = [];\n  const responses = {};\n  const requestBodySchema = {\n    type: \"object\",\n    properties: {},\n    required: []\n  };\n\n  let hasExplicitResponse = false;\n\n  function setNestedProperty(schema, pathParts, type, required) {\n    if (!pathParts.length) return;\n\n    let current = schema;\n\n    for (let i = 0; i < pathParts.length; i++) {\n      const part = pathParts[i];\n      if (!part) return;\n\n      if (!current.properties[part]) {\n        current.properties[part] = { type: \"object\", properties: {}, required: [] };\n      }\n\n      if (i === pathParts.length - 1) {\n        current.properties[part] = { type: type || \"string\" };\n        if (required && !current.required.includes(part)) {\n          current.required.push(part);\n        }\n      } else {\n        current = current.properties[part];\n      }\n    }\n  }\n\n  for (const line of lines) {\n    try {\n      const parts = line.split(/\\s+/);\n      const directive = parts[0];\n\n      if (!directive) continue;\n\n      /**\n       * PARAMETERS\n       */\n      if ([\"@query\", \"@path\", \"@header\"].includes(directive)) {\n        if (parts.length < 2) continue;\n\n        const name = parts[1];\n        const type = parts[2] || \"string\";\n        const maybeRequired = parts[3];\n        const description =\n          parts.length > 3\n            ? parts.slice(\n                maybeRequired === \"required\" || maybeRequired === \"optional\" ? 4 : 3\n              ).join(\" \")\n            : \"No description\";\n\n        let required = true; // legacy default\n\n        if (maybeRequired === \"required\" || maybeRequired === \"optional\") {\n          required = maybeRequired === \"required\";\n        }\n\n        if (directive === \"@path\") required = true;\n\n        parameters.push({\n          name,\n          in: directive.replace(\"@\", \"\"),\n          required,\n          description: description || \"No description\",\n          schema: { type }\n        });\n      }\n\n      /**\n       * BODY\n       */\n      if (directive === \"@body\") {\n        if (parts.length < 2) continue;\n\n        const name = parts[1];\n        const type = parts[2] || \"string\";\n        const maybeRequired = parts[3];\n\n        const required = maybeRequired === \"optional\" ? false : true;\n\n        const pathParts = name.split(\".\");\n        setNestedProperty(requestBodySchema, pathParts, type, required);\n      }\n\n      /**\n       * RESPONSE\n       */\n      if (directive === \"@response\") {\n        if (parts.length < 2) continue;\n\n        hasExplicitResponse = true;\n\n        const code = parts[1] || \"200\";\n        const contentType = parts[2] || \"application/json\";\n        const description = parts.slice(3).join(\" \") || \"Response\";\n\n        responses[code] = {\n          description,\n          content: contentType === \"redirect\"\n            ? undefined\n            : {\n                [contentType]: {\n                  schema: { type: \"object\" }\n                }\n              }\n        };\n      }\n\n    } catch (err) {\n      // Ignore malformed lines silently\n      continue;\n    }\n  }\n\n  const hasBodyProps = Object.keys(requestBodySchema.properties).length > 0;\n\n  return {\n    parameters,\n    requestBody: hasBodyProps\n      ? {\n          content: {\n            \"application/json\": {\n              schema: cleanSchema(requestBodySchema)\n            }\n          }\n        }\n      : undefined,\n    responses,\n    hasExplicitResponse\n  };\n}\n\nfunction cleanSchema(schema) {\n  if (!schema.required || !schema.required.length) {\n    delete schema.required;\n  }\n  return schema;\n}\n\nfunction emptyParseResult() {\n  return {\n    parameters: [],\n    requestBody: undefined,\n    responses: {},\n    hasExplicitResponse: false\n  };\n}\n\n/**\n * Infer legacy response safely\n */\nfunction inferResponsesFromNodes(webhook) {\n  let produces = \"application/json\";\n  let code = \"200\";\n\n  if (Array.isArray(webhook?.responses)) {\n    for (const r of webhook.responses) {\n      switch (r?.parameters?.respondWith) {\n        case \"text\":\n          produces = \"text/plain\";\n          break;\n        case \"redirect\":\n          produces = \"text/plain\";\n          code = \"301\";\n          break;\n        case \"json\":\n          produces = \"application/json\";\n          break;\n      }\n    }\n  }\n\n  return {\n    [code]: {\n      description: \"Successful response\",\n      content: {\n        [produces]: {\n          schema: { type: \"object\" }\n        }\n      }\n    }\n  };\n}\n\n/**\n * BUILD OPENAPI DOCUMENT\n */\nconst openapi = {\n  openapi: \"3.0.3\",\n  info: {\n    title: \"N8N Instance API\",\n    version: \"1.0.0\",\n    description: \"Auto-generated OpenAPI spec from n8n workflows\"\n  },\n  servers: [\n    {\n      url: `https://${$('Get Swagger').first().json?.headers?.host || \"n8n.instance.com\"}/webhook`\n    }\n  ],\n  paths: {}\n};\n\nfor (const item of $input.all()) {\n  const nodes = item.json?.nodes || [];\n  const connections = item.json?.connections || {};\n\n  const webhooks = nodes.filter(n => n?.type === \"n8n-nodes-base.webhook\");\n  const responseNodes = nodes.filter(n => n?.type === \"n8n-nodes-base.respondToWebhook\");\n  const targets = responseNodes.map(r => r.name);\n\n  for (const w of webhooks) {\n    try {\n      if (w?.parameters?.responseMode === \"responseNode\") {\n        const valid = findValidTargets(connections, w.name, targets);\n        w.responses = responseNodes.filter(r => valid.includes(r.name));\n      }\n\n      const path = `/${w?.parameters?.path || \"\"}`;\n      const method = (w?.parameters?.httpMethod || \"get\").toLowerCase();\n\n      if (!openapi.paths[path]) openapi.paths[path] = {};\n\n      const { parameters, requestBody, responses, hasExplicitResponse } =\n        parseAnnotations(w?.notes);\n\n      const finalResponses = hasExplicitResponse\n        ? responses\n        : inferResponsesFromNodes(w);\n\n      openapi.paths[path][method] = {\n        summary: safe(w?.name || \"Webhook\"),\n        description: `Related to workflow [${item.json?.id || \"unknown\"}]`,\n        tags: [safe(item.json?.name || \"Workflow\")],\n        parameters: parameters.length ? parameters : undefined,\n        requestBody,\n        responses: Object.keys(finalResponses).length\n          ? finalResponses\n          : inferResponsesFromNodes(w)\n      };\n    } catch (err) {\n      // Skip malformed webhook safely\n      continue;\n    }\n  }\n}\n\nconst yamlOutput = toYAML(openapi);\n\nreturn {\n  json: {\n    yamlOutput\n  }\n};"
        },
        "id": "37b03898-8b7b-4511-8a54-b6777118ea76",
        "name": "Code",
        "type": "n8n-nodes-base.code",
        "position": [
          816,
          512
        ],
        "typeVersion": 2
      },
      {
        "parameters": {
          "respondWith": "text",
          "responseBody": "=<!DOCTYPE html>\n<html>\n<head>\n  <title>Swagger UI from YAML Text</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist/swagger-ui.css\">\n</head>\n<body>\n  <div id=\"swagger-ui\"></div>\n\n  <script src=\"https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js\"></script>\n  <script src=\"https://unpkg.com/js-yaml@4.1.0/dist/js-yaml.min.js\"></script>\n  <script>\n    // Your YAML as a string (replace this with your actual variable)\n    const yamlText = `{{ $json.yamlOutput }}`;\n\n    // Parse the YAML into a JavaScript object\n    const spec = jsyaml.load(yamlText);\n\n    // Initialize Swagger UI with the parsed spec\n    const ui = SwaggerUIBundle({\n      spec: spec,\n      dom_id: \"#swagger-ui\"\n    });\n  </script>\n</body>\n</html>\n",
          "options": {}
        },
        "id": "872dbe8f-a4e2-4828-98d5-73f06ae116ad",
        "name": "Respond to Webhook",
        "type": "n8n-nodes-base.respondToWebhook",
        "position": [
          1040,
          512
        ],
        "typeVersion": 1.2
      },
      {
        "parameters": {
          "path": "swagger",
          "responseMode": "responseNode",
          "options": {}
        },
        "id": "7868c867-85c4-422d-a9f3-7424af49fd14",
        "name": "Get Swagger",
        "type": "n8n-nodes-base.webhook",
        "position": [
          368,
          512
        ],
        "webhookId": "b6873bae-3e61-4a93-9dc2-0100b497390e",
        "typeVersion": 2,
        "notes": "//@body field_name string description"
      },
      {
        "parameters": {
          "content": "## Configure webhooks\n\n### WebhookDocs: generate swagger preview of your active workflows\n\nIn order to support parameter labels you have to open the note sections of every webhook and add the following text\n\n//@body field_name string description\n//@query field_name string description\n\nAdapted from https://n8n.io/workflows/4270-webhookdocs-generate-swagger-preview-of-your-active-workflows/\n",
          "height": 244,
          "width": 480
        },
        "id": "30aa0403-c828-48eb-862b-4b258249c1e1",
        "name": "Sticky Note",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          592,
          208
        ],
        "typeVersion": 1
      }
    ],
    "connections": {
      "n8n": {
        "main": [
          [
            {
              "node": "Code",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Code": {
        "main": [
          [
            {
              "node": "Respond to Webhook",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get Swagger": {
        "main": [
          [
            {
              "node": "n8n",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "sean keery",
    "name": "Version 01ce8d13",
    "description": "",
    "autosaved": false,
    "workflowPublishHistory": [
      {
        "createdAt": "2026-03-16T19:02:54.320Z",
        "id": 142,
        "workflowId": "NsXpM2VX76Okkh43",
        "versionId": "01ce8d13-8949-4854-b8e8-7b8e0340d47b",
        "event": "activated",
        "userId": "0bf0b226-8ea5-4163-8cc1-8ca9663cae74"
      }
    ]
  }
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

getOpenAPI. Uses n8n. Webhook trigger; 5 nodes.

Source: https://github.com/skibum55/workflowsascode/blob/d156f5abccb105bd30fb8f53d6d5dbf536c3a8de/workflows/getopenapi.json — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

General

This workflow provides a secure API endpoint to remotely trigger other n8n workflows with custom data and to retrieve information about your existing workflows. It's perfect for users who want to inte

n8n
General

Stop Auditing Workflows Manually — Automate Your n8n Reports. This workflow delivers complete visibility across every automation in your n8n instance — instantly, reliably, and without opening the edi

n8n
General

If you previously upgraded to n8n version , some of your workflows might have been accidentally rewired in the wrong way. This issue affected nodes with more than one output, such as , , and .

n8n
General

A production-ready authentication workflow implementing secure user registration, login, token verification, and refresh token mechanisms. Perfect for adding authentication to any application without

Crypto, Data Table, Execute Workflow Trigger
General

Portfolio Orchestrator. Uses httpRequest. Webhook trigger; 59 nodes.

HTTP Request