Generar un portal de API auto-documentado para Webhooks usando n8n API y Bootstrap
Este es unDocument Extraction, Multimodal AIflujo de automatización del dominio deautomatización que contiene 17 nodos.Utiliza principalmente nodos como N8n, Set, Code, Html, Webhook. Usar n8n API y Bootstrap para generar una puerta de API auto-documentada para Webhooks
- •Punto final de HTTP Webhook (n8n generará automáticamente)
Nodos utilizados (17)
{
"meta": {
"instanceId": "e860732ed76ff1de8212e780b2d62bd140dee0b71e6ccf7172d54e965acae43d",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "a59b516a-e188-4364-8722-f83fd00ed0f0",
"name": "Agregar",
"type": "n8n-nodes-base.aggregate",
"position": [
120,
-300
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "3ab41431-6298-4380-aaec-d0e29f7f9cb5",
"name": "Disparador Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-700,
460
],
"webhookId": "e98f9bf5-f5ad-46fb-a93c-08218eea8d5e",
"parameters": {
"path": "api-doc",
"options": {},
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "286e23f0-44c8-46c4-bfd2-8aab6eae245b",
"name": "Configuraciones",
"type": "n8n-nodes-base.set",
"position": [
-480,
460
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "9a243289-a359-47fd-9155-a809a910b8f7",
"name": "name_doc",
"type": "string",
"value": "N8N Example Doc"
},
{
"id": "59992f15-d27d-454c-9c1f-a66f0674c7bb",
"name": "version",
"type": "string",
"value": "v.0.1"
},
{
"id": "f0633ecb-b0c8-4e55-8285-51aa05492420",
"name": "description",
"type": "string",
"value": "Example description"
},
{
"id": "2da37a16-f97a-4ec8-9d9c-13f09bea1d39",
"name": "url_base",
"type": "string",
"value": "n8n.io/"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b910c088-c3c6-4bfc-9856-5294891e4cd0",
"name": "Nota adhesiva",
"type": "n8n-nodes-base.stickyNote",
"position": [
-370,
-400
],
"parameters": {
"color": 2,
"width": 860,
"height": 260,
"content": "## Get Workflow and filter\n"
},
"typeVersion": 1
},
{
"id": "add44468-ffba-401e-a119-3e25c4870ece",
"name": "Nota adhesiva1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-120
],
"parameters": {
"color": 6,
"width": 760,
"height": 400,
"content": "## webhook for testing"
},
"typeVersion": 1
},
{
"id": "1de3f20f-152c-4f81-b42d-cc9e03f5bf0d",
"name": "Responder",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
120,
-40
],
"parameters": {
"options": {},
"respondWith": "allIncomingItems"
},
"typeVersion": 1.1
},
{
"id": "0a7a9d55-5b34-4129-952d-c982ebcc3d7c",
"name": "ResponderHTML",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
180,
460
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "={{$json.html}}"
},
"typeVersion": 1.1
},
{
"id": "f85d91a0-16ec-4f79-870a-dfc24f954469",
"name": "CrearHTMLCompleto",
"type": "n8n-nodes-base.html",
"position": [
-40,
460
],
"parameters": {
"html": "<html>\n<head>\n <title>{{$('Configs').item.json.name_doc}}</title>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css\">\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n <style>\n :root {\n --bs-body-bg: #1a1a1a;\n --bs-body-color: #e0e0e0;\n --bs-border-color: #444;\n --bs-accordion-bg: #2b2b2b;\n --bs-accordion-color: #e0e0e0;\n --bs-accordion-button-color: #e0e0e0;\n --bs-accordion-border-color: #444;\n --bs-accordion-button-active-bg: #3c3c3c;\n --bs-accordion-button-active-color: #ffffff;\n --bs-accordion-button-focus-border-color: #007bff;\n --bs-accordion-button-focus-box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25);\n }\n .method {\n display: inline-block;\n padding: 0.3em 0.8em;\n border-radius: 0.25rem;\n font-weight: bold;\n font-size: 0.9em;\n color: white;\n text-transform: uppercase;\n margin-right: 1rem;\n }\n .method-post { background-color: #49cc90; }\n .method-get { background-color: #61affe; }\n .endpoint-path {\n font-family: monospace;\n font-size: 1.1em;\n color: #ccc;\n }\n pre {\n background-color: #161B22;\n padding: 1rem;\n border-radius: 0.25rem;\n white-space: pre-wrap;\n word-break: break-all;\n border: 1px solid var(--bs-border-color);\n }\n code { color: #c9d1d9; }\n .accordion-button:not(.collapsed) {\n box-shadow: inset 0 -1px 0 var(--bs-accordion-border-color);\n }\n </style>\n</head>\n<body class=\"p-4\">\n <div class=\"container\">\n <div class=\"d-flex align-items-baseline mb-4\">\n <h1 class=\"text-white me-3\">{{$('Configs').item.json.name_doc}}</h1>\n <p class=\"text-white-50 me-3\">{{$('Configs').item.json.description}}</p>\n <span class=\"badge bg-secondary\">{{$('Configs').item.json.version}}</span>\n </div>\n \n {{ $json.htmlContent || `\n<div class=\"alert alert-danger d-flex align-items-center mt-4\" role=\"alert\">\n <i class=\"bi bi-x-octagon-fill me-3\" style=\"font-size: 2rem;\"></i>\n <div>\n <h4 class=\"alert-heading\">Documentation Generation Failed!</h4>\n <p>An error occurred while trying to generate the API documentation from the sub-workflow.</p>\n <hr>\n <p class=\"mb-1\"><strong>Please check the following:</strong></p>\n <ul>\n <li>Ensure the sub-workflow (the one that generates the accordion) executed successfully and produced an output named <code>htmlContent</code>.</li>\n <li>Verify that at least one of your workflows is active and contains a correctly configured <code>API_DOCS</code> node.</li>\n <li>Check the n8n execution logs for more detailed error messages from the sub-workflow.</li>\n </ul>\n </div>\n</div>\n`}}\n\n\n </div>\n <footer class=\"mt-5 pt-4 text-center text-white-50\">\n <p>\n Created by <a href=\"https://www.linkedin.com/in/matheus-pedrosa-custodio/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"link-light text-decoration-none\">\n <i class=\"bi bi-linkedin\"></i> Matheus Pedrosa\n </a>\n </p>\n</footer>\n \n <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js\"></script>\n</body>\n</html>"
},
"typeVersion": 1.2
},
{
"id": "0bd66854-cf1b-46b5-ad75-e0d7ef9b71d4",
"name": "EjecutarSubflujo",
"type": "n8n-nodes-base.executeWorkflow",
"onError": "continueRegularOutput",
"position": [
-260,
460
],
"parameters": {
"options": {
"waitForSubWorkflow": true
},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "y2xOdk7RyRoS2sgF",
"cachedResultName": "Documentation"
},
"workflowInputs": {
"value": {},
"schema": [
{
"id": "url_base",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "url_base",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.2
},
{
"id": "43a02f1b-9f18-4148-9705-2c3347997cfa",
"name": "Ejecutar",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-320,
-300
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "url_base"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "91ad9f92-adf3-4a8c-af48-3d1e5cd06678",
"name": "ObtenerFlujos",
"type": "n8n-nodes-base.n8n",
"position": [
-100,
-300
],
"parameters": {
"filters": {
"tags": "",
"activeWorkflows": true
},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "fTE1pX6nL40KSIdW",
"name": "n8n account"
}
},
"retryOnFail": true,
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "c28819b2-2e5d-4732-9a68-0d5045dcb59e",
"name": "FiltrarFlujos",
"type": "n8n-nodes-base.code",
"position": [
340,
-300
],
"parameters": {
"jsCode": "const allWorkflows = $('Aggregate').last().json.data;\nlet html = '';\n\nconst documentedWebhookWorkflows = allWorkflows.filter(item => {\n if (!item.nodes || item.nodes.length === 0) {\n return false;\n }\n const hasWebhook = item.nodes.some(node => node.type === 'n8n-nodes-base.webhook');\n const hasDocsNote = item.nodes.some(node => node.type === 'n8n-nodes-base.set' && node.name === 'API_DOCS');\n return hasWebhook && hasDocsNote;\n});\n\n\nif (documentedWebhookWorkflows.length > 0) {\n html += '<div class=\"accordion\" id=\"docsAccordion\">';\n\n const n8nBaseUrl = $(\"Execute\").last().json.url_base || 'https://n8n.io/';\n\n for (const workflow of documentedWebhookWorkflows) {\n const docsNode = workflow.nodes.find(n => n.name === 'API_DOCS');\n\n if (docsNode) {\n const jsonString = docsNode.parameters.jsonOutput || JSON.stringify(docsNode.parameters.docsData);\n let docsData = {};\n let webhookNode = null;\n\n try {\n docsData = JSON.parse(jsonString);\n webhookNode = workflow.nodes.find(n => { return n.type === 'n8n-nodes-base.webhook' && n.name === docsData.webhook });\n } catch (e) {\n console.error(`Erro ao parsear JSON no workflow: ${workflow.name}`, e);\n continue;\n }\n\n if(webhookNode){\n const method = (docsData.method || \"POST\").toUpperCase();\n const uniqueId = webhookNode.parameters.path.replace(/[^a-zA-Z0-9]/g, '');\n\n // --- make CURL\n const fullUrl = `${n8nBaseUrl}webhook/${webhookNode.parameters.path}`;\n\n let curlCommand = `curl -X ${method}\\\\`;\n if (method === 'POST' || method === 'PUT' || method === 'PATCH') {\n const requestBodyString = JSON.stringify(docsData.requestBody || {});\n curlCommand += `\n -H 'Content-Type: application/json' \\\\\n -d '${requestBodyString}' \\\\`;\n }\n curlCommand += `\n ${fullUrl}`;\n\n html += `\n <div class=\"accordion-item\">\n <h2 class=\"accordion-header\" id=\"heading-${uniqueId}\">\n <button class=\"accordion-button collapsed\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapse-${uniqueId}\" aria-expanded=\"false\" aria-controls=\"collapse-${uniqueId}\">\n <span class=\"method method-${method.toLowerCase()}\">${method}</span>\n <span class=\"endpoint-path\">/webhook/${webhookNode.parameters.path}</span>\n </button>\n </h2>\n <div id=\"collapse-${uniqueId}\" class=\"accordion-collapse collapse\" aria-labelledby=\"heading-${uniqueId}\" data-bs-parent=\"#docsAccordion\">\n <div class=\"accordion-body\">\n <p><strong>${docsData.summary || 'No summary'}</strong></p>\n <p>${docsData.description || 'No description.'}</p>\n <hr style=\"border-color: var(--bs-border-color);\">\n \n <h6>cURL Command:</h6>\n <pre><code>${curlCommand}</code></pre>\n \n <h6>Request Body:</h6>\n <pre><code>${JSON.stringify(docsData.requestBody || {}, null, 2)}</code></pre>\n <h6>Success Response (${docsData.successCode || 'N/A'}):</h6>\n <pre><code>${JSON.stringify(docsData.successResponse || {}, null, 2)}</code></pre>\n <h6>Error Response (${docsData.errorCode || 'N/A'}):</h6>\n <pre><code>${JSON.stringify(docsData.errorResponse || {}, null, 2)}</code></pre>\n </div>\n </div>\n </div>\n `;\n }\n }\n }\n html += '</div>';\n} else {\n html += '<div class=\"alert alert-info\">Nenhum endpoint documentado foi encontrado.</div>';\n}\n\nreturn [{\n json: {\n htmlContent: html\n }\n}];"
},
"typeVersion": 2
},
{
"id": "78526a12-613c-4530-9a6f-bb612036addf",
"name": "Nota adhesiva2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-940,
-400
],
"parameters": {
"color": 4,
"width": 540,
"height": 700,
"content": "### Live n8n API Documentation Generator\n\n**Author:** Matheus Pedrosa | https://www.linkedin.com/in/matheus-pedrosa-custodio/\n**Version:** 1.0\n**Date:** 2025-08-21\n\n## Summary\nThis is a \"meta-workflow\" that acts as an engine to automatically generate a live, interactive HTML documentation page for your n8n instance's webhooks. It scans all active workflows, finds the ones designated for documentation, and renders a single, professional, dark-themed page.\n\n## Key Feature\nThe core logic relies on a **convention-based discovery**. To include a webhook in the documentation, you simply add a `Set` node named **`API_DOCS`** to that workflow and fill in a JSON object with the endpoint's metadata (summary, request body, responses, etc.). This generator then automatically parses that data to build the documentation page.\n\n## Setup (Quick Checklist)\n1. **`API_DOCS` Nodes:** In every workflow you want to document, add a `Set` node named `API_DOCS` and provide the necessary JSON metadata (especially the `webhookPath`).\n2. **`GetWorkflows` Node:** Configure this node with your `n8n API` credentials. It needs permission to read all workflows.\n3. **`Configs` Node:** Customize the global settings for your documentation page, such as the main title (`name_doc`), description, and version.\n4. **`Webhook` Trigger:** The URL of this workflow's trigger is the public URL for your final documentation page."
},
"typeVersion": 1
},
{
"id": "602d0cd7-558e-4360-b385-bf9824f9278b",
"name": "Disparador Webhook1",
"type": "n8n-nodes-base.webhook",
"position": [
-320,
-40
],
"webhookId": "15105da5-96ca-472f-a7db-2ddc2e8b0891",
"parameters": {
"path": "15105da5-96ca-472f-a7db-2ddc2e8b0891",
"options": {},
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "6283f30d-d1eb-4a7b-9f97-73834d7c060f",
"name": "API_DOCS",
"type": "n8n-nodes-base.set",
"position": [
-320,
120
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"expose\": true,\n \"webhook\": \"Webhook1\",\n \"summary\": \"Title test\",\n \"method\":\"get\",\n \"description\": \"Hey community, look at this cool workflow I'm doing.\",\n \"tags\": [\n \"test\",\n \"tag_0101\"\n ],\n \"requestBody\": {},\n \"successCode\": 200,\n \"successResponse\": {\n \"status\": 200,\n \"message\": \"Workflow start\"\n },\n \"errorCode\": 400,\n \"errorResponse\": {\n \"status\": 500,\n \"message\": \"Workflow error\"\n }\n}"
},
"typeVersion": 3.4
},
{
"id": "57843c34-afb5-401e-86dc-8aa22c242263",
"name": "RespuestaFalsa",
"type": "n8n-nodes-base.code",
"position": [
-100,
-40
],
"parameters": {
"jsCode": "return {\n \"status\": 200,\n \"message\": \"Workflow start\"\n }"
},
"typeVersion": 2
},
{
"id": "29b6b6ea-917b-47a3-b8a1-ff017b0a10a5",
"name": "Nota adhesiva3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-940,
400
],
"parameters": {
"width": 150,
"height": 200,
"content": "### Create documentation page\n\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"286e23f0-44c8-46c4-bfd2-8aab6eae245b": {
"main": [
[
{
"node": "0bd66854-cf1b-46b5-ad75-e0d7ef9b71d4",
"type": "main",
"index": 0
}
]
]
},
"43a02f1b-9f18-4148-9705-2c3347997cfa": {
"main": [
[
{
"node": "91ad9f92-adf3-4a8c-af48-3d1e5cd06678",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "286e23f0-44c8-46c4-bfd2-8aab6eae245b",
"type": "main",
"index": 0
}
]
]
},
"Webhook1": {
"main": [
[
{
"node": "57843c34-afb5-401e-86dc-8aa22c242263",
"type": "main",
"index": 0
}
]
]
},
"a59b516a-e188-4364-8722-f83fd00ed0f0": {
"main": [
[
{
"node": "c28819b2-2e5d-4732-9a68-0d5045dcb59e",
"type": "main",
"index": 0
}
]
]
},
"57843c34-afb5-401e-86dc-8aa22c242263": {
"main": [
[
{
"node": "1de3f20f-152c-4f81-b42d-cc9e03f5bf0d",
"type": "main",
"index": 0
}
]
]
},
"91ad9f92-adf3-4a8c-af48-3d1e5cd06678": {
"main": [
[
{
"node": "a59b516a-e188-4364-8722-f83fd00ed0f0",
"type": "main",
"index": 0
}
]
]
},
"f85d91a0-16ec-4f79-870a-dfc24f954469": {
"main": [
[
{
"node": "0a7a9d55-5b34-4129-952d-c982ebcc3d7c",
"type": "main",
"index": 0
}
]
]
},
"c28819b2-2e5d-4732-9a68-0d5045dcb59e": {
"main": [
[]
]
},
"0bd66854-cf1b-46b5-ad75-e0d7ef9b71d4": {
"main": [
[
{
"node": "f85d91a0-16ec-4f79-870a-dfc24f954469",
"type": "main",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Avanzado - Extracción de documentos, IA Multimodal
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Matheus Pedrosa
@julinhoI am a software engineer specializing in automations, with extensive experience on the N8N platform. With solid skills in JavaScript, Go, .NET, and C#, I am equipped to develop efficient and scalable solutions.
Compartir este flujo de trabajo