This workflow corresponds to n8n.io template #14093 — we link there as the canonical source.
This workflow follows the Gmail → Google Drive 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 →
{
"name": "API Documentation \u2014 Generator",
"tags": [],
"nodes": [
{
"id": "8655873a-3376-45e4-8311-68a646570e47",
"name": "Extract from File2",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-208,
544
],
"parameters": {
"options": {},
"operation": "text"
},
"typeVersion": 1.1
},
{
"id": "a0edb4ab-f3e6-4ff0-be75-a7ef3d6bfb39",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1520,
448
],
"parameters": {},
"typeVersion": 1
},
{
"id": "75a5f62e-1479-4845-84ce-283291ad65a5",
"name": "Sticky Note \u2014 Step 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
256
],
"parameters": {
"color": 4,
"width": 560,
"height": 344,
"content": "### \ud83d\udcc1 Step 1 \u2014 Load Files\nTrigger manually, then fetch all files from the configured Google Drive folder and filter down to `.h` files only.\n\n\u2699\ufe0f **To configure:** Open **Get all files from folder** and update the `queryString` with your own Google Drive folder ID."
},
"typeVersion": 1
},
{
"id": "046dba14-4d05-491a-a311-2446963de7dd",
"name": "Sticky Note \u2014 Step 8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-848,
256
],
"parameters": {
"width": 252,
"height": 424,
"content": "### \ud83d\udd01 Step 2 \u2014 Loop\nProcess one `.h` file at a time using **Split in Batches**.\n\nThe loop returns here after each file is saved to Drive, until all files are processed. When done, it branches to the email notification."
},
"typeVersion": 1
},
{
"id": "77d98362-d5c4-4be3-967d-da5fc59d573b",
"name": "Sticky Note \u2014 Step 9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-464,
416
],
"parameters": {
"width": 372,
"height": 280,
"content": "### \ud83d\udce5 Step 3 \u2014 Download & Read\nDownloads the binary file from Google Drive and extracts its raw text content, ready to be sent to GPT-4o."
},
"typeVersion": 1
},
{
"id": "6493ac89-3f40-45eb-822f-988d82f4a570",
"name": "Sticky Note \u2014 Step 10",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
256
],
"parameters": {
"color": 2,
"width": 248,
"height": 392,
"content": "### \ud83e\udd16 Step 4 \u2014 AI Extraction\nSends the raw `.h` file content to **GPT-4o** with a structured prompt.\n\nGPT returns a strict JSON object with:\n`overview` \u00b7 `functions` \u00b7 `enumerators` \u00b7 `data_types` \u00b7 `constants` \u00b7 `notes`\n\n\u26a0\ufe0f Requires an **OpenAI credential** with GPT-4o access."
},
"typeVersion": 1
},
{
"id": "11cab0db-26db-4a64-ae73-c7601eefdf75",
"name": "Sticky Note \u2014 Step 11",
"type": "n8n-nodes-base.stickyNote",
"position": [
288,
320
],
"parameters": {
"width": 224,
"height": 360,
"content": "### \ud83d\udd27 Step 5 \u2014 Parse Response\nStrips any markdown fences from the GPT response and parses it as JSON.\n\n\ud83d\udca1 If parsing fails, the raw GPT text is placed in `overview` so you can debug what GPT returned."
},
"typeVersion": 1
},
{
"id": "f248ab44-1130-49c1-90f2-ec65567cc480",
"name": "Sticky Note \u2014 Step 12",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
224
],
"parameters": {
"color": 5,
"width": 244,
"height": 472,
"content": "### \ud83c\udfa8 Step 6 \u2014 Generate HTML\nBuilds a fully styled HTML documentation file with:\n- Sidebar navigation\n- Hero header with metadata\n- Function cards with signature, params table & examples\n- Data types, enums, constants sections\n\nOutputs as a **binary `.html` file** ready for Google Drive upload."
},
"typeVersion": 1
},
{
"id": "ebb3f0fc-5d41-4022-a8d2-58b7b951c215",
"name": "Sticky Note \u2014 Step 13",
"type": "n8n-nodes-base.stickyNote",
"position": [
848,
224
],
"parameters": {
"color": 4,
"width": 252,
"height": 472,
"content": "### \ud83d\udcbe Step 7 \u2014 Save to Drive\nUploads the generated `DOC_filename.html` file to the configured output folder in Google Drive.\n\n\u2699\ufe0f **To configure:** Update the `folderId` in this node with your output folder ID."
},
"typeVersion": 1
},
{
"id": "0410927e-234a-46f5-aca6-a9a60eeb56d0",
"name": "Sticky Note \u2014 Email1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
-32
],
"parameters": {
"color": 2,
"width": 440,
"height": 340,
"content": "### \u2709\ufe0f Completion Email\nOnce all files are processed, the **Split in Batches** node exits the loop and triggers this branch.\n\nBuilds a summary email and sends it via **Gmail** to notify that all docs have been generated.\n\n\u2699\ufe0f Update the recipient email in **Send completion email**."
},
"typeVersion": 1
},
{
"id": "240eb434-776f-4195-a456-15a83b8f5d5a",
"name": "Sticky Note \u2014 Overview1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
80
],
"parameters": {
"color": 5,
"width": 396,
"height": 678,
"content": "# \ud83d\udcc4 API Documentation Generator\n\nAutomatically generates **beautiful HTML documentation** from C header (`.h`) files stored in Google Drive.\n\n**How it works:**\n1. Reads all files from a specified Google Drive folder\n2. Filters to `.h` header files only\n3. Processes each file one at a time through GPT-4o\n4. GPT extracts functions, types, enums, constants & notes as structured JSON\n5. Generates a professional HTML doc and saves it back to Google Drive\n6. Sends a completion email when all files are processed\n\n**Requirements:**\n- Google Drive OAuth2 credentials\n- OpenAI API credentials\n- Gmail credentials\n- Update the Google Drive folder ID in the **Get all files** node"
},
"typeVersion": 1
},
{
"id": "f63de4ea-6d88-4f10-a195-feb2f7b5f3ea",
"name": "Get all files from folder",
"type": "n8n-nodes-base.googleDrive",
"position": [
-1328,
448
],
"parameters": {
"filter": {},
"options": {},
"resource": "fileFolder",
"returnAll": true,
"queryString": "'YOUR_FOLDER_ID' in parents and trashed=false",
"searchMethod": "query"
},
"typeVersion": 3
},
{
"id": "14c89229-0249-4e47-a46c-1480e961b59c",
"name": "Filter .h files only",
"type": "n8n-nodes-base.code",
"position": [
-1104,
448
],
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\nfor (const item of items) {\n const name = (item.json.name || '').toLowerCase();\n if (name.endsWith('.h')) {\n results.push({ json: { id: item.json.id, fileName: item.json.name } });\n }\n}\nif (results.length === 0) throw new Error('No .h header files found.');\nreturn results;"
},
"typeVersion": 2
},
{
"id": "b2362399-2709-42d6-a294-5d834562a4b7",
"name": "Process one file at a time",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-768,
528
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "e74de2b2-e2bf-4e9f-b191-e703a914cb15",
"name": "Download file",
"type": "n8n-nodes-base.googleDrive",
"position": [
-432,
544
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "477c9a71-e6b8-4125-8dca-9987320fda1a",
"name": "Build email body",
"type": "n8n-nodes-base.code",
"position": [
-400,
160
],
"parameters": {
"jsCode": "const items = $input.all();\nfor (const item of items) {\n const fileName = item.json.pdfName || item.json.name || 'DOC.pdf';\n item.json.subject = 'API Documentation PDF Ready \u2014 XYZ Technologies';\n item.json.message = `Hello,\n\nYour C header file API documentation PDF has been generated by XYZ Technologies.\n\nFile: ${fileName}\nUploaded to: Output folder in Google Drive\nGenerated: ${new Date().toUTCString()}\n\nThe PDF includes:\n - Overview\n - Functions (signatures, parameters, return values, examples)\n - Enumerators\n - Data Types & Structures\n - Constants\n - Notes\n\nBest regards,\nXYZ Technologies Automation`;\n}\nreturn items;"
},
"typeVersion": 2
},
{
"id": "e350754f-4db4-407b-a6d0-ce199df685bd",
"name": "Send completion email",
"type": "n8n-nodes-base.gmail",
"position": [
-192,
160
],
"parameters": {
"sendTo": "your@email.com",
"message": "={{ $json.message }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"typeVersion": 2.2
},
{
"id": "8e43b277-9554-43e2-bc04-db84eadb6b51",
"name": "Extract structured docs (GPT-4o)",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
16,
544
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "GPT-4O"
},
"options": {},
"responses": {
"values": [
{
"role": "system",
"content": "You are a senior technical writer for embedded systems at XYZ Technologies. Given a C header (.h) file, extract and document everything as strict JSON \u2014 no markdown, no explanation, raw JSON only.\n\nReturn exactly this structure:\n{\n \"overview\": \"<2-3 sentence summary>\",\n \"functions\": [\n {\n \"name\": \"<function name>\",\n \"signature\": \"<full prototype>\",\n \"description\": \"<what it does>\",\n \"parameters\": [\n { \"name\": \"<param>\", \"type\": \"<type>\", \"description\": \"<desc>\" }\n ],\n \"returns\": \"<return type and meaning>\",\n \"example\": \"<minimal C usage>\"\n }\n ],\n \"enumerators\": [\n {\n \"name\": \"<enum type name>\",\n \"description\": \"<purpose>\",\n \"values\": [\n { \"name\": \"<VALUE>\", \"value\": \"<integer>\", \"description\": \"<meaning>\" }\n ]\n }\n ],\n \"data_types\": [\n {\n \"name\": \"<type name>\",\n \"kind\": \"<struct|union|typedef|bitfield>\",\n \"description\": \"<purpose>\",\n \"members\": [\n { \"name\": \"<field>\", \"type\": \"<type>\", \"description\": \"<meaning>\" }\n ]\n }\n ],\n \"constants\": [\n { \"name\": \"<CONSTANT_NAME>\", \"value\": \"<value>\", \"description\": \"<meaning>\" }\n ],\n \"notes\": \"<usage notes, thread safety, prerequisites>\"\n}\n\nIf a section has no items return []. Return ONLY the JSON object."
},
{
"content": "=File: {{ $('Process one file at a time').item.json.fileName }}\n\n{{ $('Extract from File2').item.json.data }}"
}
]
},
"builtInTools": {}
},
"typeVersion": 2.1
},
{
"id": "f463a4fa-5996-4e9d-8f10-bc2f9c54d4ad",
"name": "Parse JSON response",
"type": "n8n-nodes-base.code",
"position": [
352,
544
],
"parameters": {
"jsCode": "// \u2500\u2500 Parse JSON response \u2014 handles all known OpenAI/n8n response shapes \u2500\u2500\u2500\u2500\u2500\u2500\nconst item = $input.first();\n\n// Log the full response so you can inspect it in n8n's output panel\n// (remove once working)\nconsole.log('RAW ITEM KEYS:', JSON.stringify(Object.keys(item.json || {})));\n\nconst raw =\n // n8n OpenAI node (AI Agent / Chat Model)\n item.json?.choices?.[0]?.message?.content ||\n // OpenAI Responses API shape\n item.json?.output?.[0]?.content?.[0]?.text ||\n // Fallback: sometimes n8n wraps it under .text or .message\n item.json?.message?.content ||\n item.json?.text ||\n // Last resort: if the whole json IS the parsed object already\n (item.json?.overview ? JSON.stringify(item.json) : '{}');\n\nlet parsed;\ntry {\n const cleaned = raw\n .replace(/^```(?:json)?\\r?\\n?/, '')\n .replace(/\\r?\\n?```$/, '')\n .trim();\n parsed = JSON.parse(cleaned);\n} catch (e) {\n // If JSON parse fails, put the raw text in overview so you can see what GPT returned\n parsed = {\n overview: 'JSON parse failed. Raw response: ' + raw.slice(0, 500),\n functions: [],\n enumerators: [],\n data_types: [],\n constants: [],\n notes: ''\n };\n}\n\n// Try multiple places for fileName\nconst fileName =\n $('Process one file at a time')?.item?.json?.fileName ||\n $('Process one file at a time1')?.item?.json?.fileName ||\n item.json?.fileName ||\n 'unknown.h';\n\nreturn [{ json: { ...parsed, fileName } }];\n"
},
"typeVersion": 2
},
{
"id": "f7a197b7-3470-4ad5-a2bb-fa913c461969",
"name": "Generate HTML",
"type": "n8n-nodes-base.code",
"position": [
608,
544
],
"parameters": {
"jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// GENERATE HTML FILE \u2014 outputs binary HTML file \u2192 save directly to Google Drive\n// Replace \"Generate PDF (zero dependencies)1\" with this node.\n// Then wire: this node \u2192 Save to Google Drive (upload file)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst doc = $input.first().json;\nconst fileName = doc.fileName || 'unknown.h';\nconst baseName = fileName.replace(/\\.[^/.]+$/, '');\nconst htmlName = 'DOC_' + baseName + '.html';\nconst today = new Date().toISOString().slice(0, 10);\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst esc = s => String(s || '')\n .replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');\n\nfunction funcBlock(fn) {\n const params = fn.parameters || [];\n const paramRows = params.map(p => `\n <tr>\n <td><code>${esc(p.name)}</code></td>\n <td><code>${esc(p.type)}</code></td>\n <td>${esc(p.description)}</td>\n </tr>`).join('');\n\n return `\n <div class=\"card fn-block\">\n <div class=\"fn-name\">${esc(fn.name || '')}</div>\n <p class=\"fn-desc\">${esc(fn.description || '')}</p>\n ${fn.signature ? `<div class=\"label\">Signature</div><pre><code>${esc(fn.signature)}</code></pre>` : ''}\n ${params.length ? `\n <div class=\"label\">Parameters</div>\n <div class=\"table-wrap\"><table>\n <thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>\n <tbody>${paramRows}</tbody>\n </table></div>` : ''}\n ${fn.returns ? `<div class=\"label\">Returns</div><p class=\"returns\">${esc(fn.returns)}</p>` : ''}\n ${fn.example ? `<div class=\"label\">Example</div><pre><code>${esc(fn.example)}</code></pre>` : ''}\n </div>`;\n}\n\nfunction dtBlock(dt) {\n const members = dt.members || [];\n const rows = members.map(m => `\n <tr>\n <td><code>${esc(m.name)}</code></td>\n <td><code>${esc(m.type)}</code></td>\n <td>${esc(m.description)}</td>\n </tr>`).join('');\n return `\n <div class=\"card\">\n <div class=\"fn-name\">${esc(dt.name || '')} <span class=\"kind-tag\">${esc(dt.kind || '')}</span></div>\n <p>${esc(dt.description || '')}</p>\n ${members.length ? `\n <div class=\"label\">Members</div>\n <div class=\"table-wrap\"><table>\n <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>\n <tbody>${rows}</tbody>\n </table></div>` : ''}\n </div>`;\n}\n\nfunction enumBlock(en) {\n const vals = en.values || [];\n const rows = vals.map(v => `\n <tr>\n <td><code>${esc(v.name)}</code></td>\n <td>${esc(v.value)}</td>\n <td>${esc(v.description)}</td>\n </tr>`).join('');\n return `\n <div class=\"card\">\n <div class=\"fn-name\">${esc(en.name || '')}</div>\n <p>${esc(en.description || '')}</p>\n ${vals.length ? `\n <div class=\"table-wrap\"><table>\n <thead><tr><th>Enumerator</th><th>Value</th><th>Description</th></tr></thead>\n <tbody>${rows}</tbody>\n </table></div>` : ''}\n </div>`;\n}\n\nconst fns = doc.functions || [];\nconst enums = doc.enumerators || [];\nconst dts = doc.data_types || [];\nconst consts = doc.constants || [];\n\nconst empty = label => `<p class=\"empty-state\">No ${label} found in this header file.</p>`;\n\nconst fnHtml = fns.length ? fns.map(funcBlock).join('') : empty('functions');\nconst enumHtml = enums.length ? enums.map(enumBlock).join('') : empty('enumerators');\nconst dtHtml = dts.length ? dts.map(dtBlock).join('') : empty('data types');\nconst constHtml = consts.length ? `\n <div class=\"card\">\n <div class=\"table-wrap\"><table>\n <thead><tr><th>Constant Name</th><th>Value</th><th>Description</th></tr></thead>\n <tbody>${consts.map(c=>`<tr><td><code>${esc(c.name)}</code></td><td>${esc(c.value)}</td><td>${esc(c.description)}</td></tr>`).join('')}</tbody>\n </table></div></div>` : empty('constants');\n\nconst tocItems = [\n ['1','Overview'],['2','Functions'],['3','Enumerators'],\n ['4','Data Types & Structures'],['5','Constants'],['6','Notes']\n];\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>${esc(baseName)} \u2014 API Documentation</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400&display=swap\" rel=\"stylesheet\">\n<style>\n :root {\n --navy: #0D1C28;\n --blue: #1567BF;\n --cyan: #00B0FF;\n --light: #E8EFF9;\n --muted: #8C9BA5;\n --text: #1A2530;\n --border: #CBD5E1;\n --code-bg:#F0F7EC;\n --code-fg:#1B5E20;\n --white: #FFFFFF;\n --card-bg:#FAFCFF;\n --sidebar-w: 240px;\n }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html { scroll-behavior: smooth; }\n\n body {\n font-family: 'IBM Plex Sans', sans-serif;\n font-size: 14px;\n line-height: 1.65;\n color: var(--text);\n background: #F4F7FB;\n display: flex;\n min-height: 100vh;\n }\n\n /* \u2500\u2500 Sidebar \u2500\u2500 */\n .sidebar {\n width: var(--sidebar-w);\n min-height: 100vh;\n background: var(--navy);\n position: fixed;\n top: 0; left: 0;\n display: flex;\n flex-direction: column;\n z-index: 100;\n }\n\n .sidebar-logo {\n padding: 24px 20px 16px;\n border-bottom: 1px solid rgba(255,255,255,0.08);\n }\n .sidebar-logo .brand {\n font-size: 17px;\n font-weight: 700;\n color: var(--white);\n letter-spacing: 0.3px;\n }\n .sidebar-logo .brand span { color: var(--cyan); font-weight: 300; }\n .sidebar-logo .file-name {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 10px;\n color: var(--muted);\n margin-top: 4px;\n }\n\n .sidebar-nav {\n padding: 16px 0;\n flex: 1;\n overflow-y: auto;\n }\n .nav-label {\n font-size: 9px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 1.2px;\n color: var(--muted);\n padding: 8px 20px 4px;\n }\n .nav-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 20px;\n color: rgba(255,255,255,0.65);\n text-decoration: none;\n font-size: 13px;\n font-weight: 400;\n transition: all 0.15s;\n border-left: 3px solid transparent;\n }\n .nav-item:hover {\n color: var(--white);\n background: rgba(255,255,255,0.06);\n border-left-color: var(--cyan);\n }\n .nav-item .num {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 10px;\n color: var(--cyan);\n min-width: 16px;\n }\n\n .sidebar-footer {\n padding: 14px 20px;\n border-top: 1px solid rgba(255,255,255,0.08);\n font-size: 10px;\n color: var(--muted);\n }\n\n /* \u2500\u2500 Main content \u2500\u2500 */\n .main {\n margin-left: var(--sidebar-w);\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n /* \u2500\u2500 Top bar \u2500\u2500 */\n .topbar {\n background: var(--white);\n border-bottom: 1px solid var(--border);\n padding: 14px 40px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 50;\n }\n .topbar-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--navy);\n }\n .topbar-meta {\n font-size: 11px;\n color: var(--muted);\n font-family: 'IBM Plex Mono', monospace;\n }\n .badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n background: var(--light);\n color: var(--blue);\n font-size: 10px;\n font-weight: 600;\n padding: 3px 8px;\n border-radius: 4px;\n letter-spacing: 0.3px;\n }\n\n /* \u2500\u2500 Cover hero \u2500\u2500 */\n .hero {\n background: linear-gradient(135deg, var(--navy) 0%, #152E45 60%, #1B3A52 100%);\n padding: 56px 40px 48px;\n color: var(--white);\n position: relative;\n overflow: hidden;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -40px; right: -40px;\n width: 280px; height: 280px;\n border-radius: 50%;\n background: radial-gradient(circle, rgba(0,176,255,0.12) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 2px;\n color: var(--cyan);\n margin-bottom: 12px;\n }\n .hero-title {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 32px;\n font-weight: 600;\n letter-spacing: -0.5px;\n margin-bottom: 8px;\n }\n .hero-subtitle {\n font-size: 15px;\n color: rgba(255,255,255,0.55);\n font-weight: 300;\n margin-bottom: 28px;\n }\n .hero-meta {\n display: flex;\n gap: 24px;\n flex-wrap: wrap;\n }\n .hero-meta-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .hero-meta-item .meta-key {\n font-size: 9px;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--muted);\n }\n .hero-meta-item .meta-val {\n font-size: 12px;\n font-family: 'IBM Plex Mono', monospace;\n color: rgba(255,255,255,0.8);\n }\n .hero-accent {\n height: 2px;\n background: linear-gradient(90deg, var(--cyan), transparent);\n margin-bottom: 28px;\n width: 80px;\n }\n\n /* \u2500\u2500 Content area \u2500\u2500 */\n .content {\n padding: 0 40px 60px;\n max-width: 900px;\n }\n\n /* \u2500\u2500 Section \u2500\u2500 */\n .section {\n margin-top: 48px;\n }\n .section-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 12px;\n border-bottom: 2px solid var(--border);\n }\n .section-num {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 11px;\n font-weight: 600;\n color: var(--cyan);\n background: var(--navy);\n padding: 3px 8px;\n border-radius: 4px;\n }\n .section-title {\n font-size: 18px;\n font-weight: 700;\n color: var(--navy);\n letter-spacing: -0.3px;\n }\n\n /* \u2500\u2500 Card \u2500\u2500 */\n .card {\n background: var(--white);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 22px 24px;\n margin-bottom: 14px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.05);\n }\n\n /* \u2500\u2500 Function name \u2500\u2500 */\n .fn-name {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 15px;\n font-weight: 600;\n color: var(--blue);\n margin-bottom: 6px;\n }\n .fn-desc {\n color: #4A5568;\n margin-bottom: 14px;\n font-size: 13.5px;\n }\n .kind-tag {\n font-size: 11px;\n font-weight: 400;\n font-family: 'IBM Plex Sans', sans-serif;\n color: var(--muted);\n background: var(--light);\n padding: 1px 7px;\n border-radius: 3px;\n vertical-align: middle;\n margin-left: 6px;\n }\n\n /* \u2500\u2500 Label \u2500\u2500 */\n .label {\n font-size: 9px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 1.2px;\n color: var(--blue);\n margin: 14px 0 6px;\n }\n\n /* \u2500\u2500 Code \u2500\u2500 */\n pre {\n background: var(--code-bg);\n border-left: 3px solid var(--cyan);\n border-radius: 0 6px 6px 0;\n padding: 12px 16px;\n font-family: 'IBM Plex Mono', monospace;\n font-size: 12px;\n color: var(--code-fg);\n white-space: pre-wrap;\n word-break: break-all;\n margin-bottom: 6px;\n overflow-x: auto;\n }\n code {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 12px;\n background: var(--code-bg);\n color: var(--code-fg);\n padding: 1px 5px;\n border-radius: 3px;\n }\n pre code { background: none; padding: 0; }\n\n /* \u2500\u2500 Returns \u2500\u2500 */\n .returns {\n font-family: 'IBM Plex Mono', monospace;\n font-size: 12px;\n color: #555;\n }\n\n /* \u2500\u2500 Tables \u2500\u2500 */\n .table-wrap { overflow-x: auto; }\n table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n thead tr {\n background: var(--navy);\n color: var(--white);\n }\n thead th {\n padding: 9px 12px;\n text-align: left;\n font-weight: 600;\n font-size: 11px;\n letter-spacing: 0.3px;\n white-space: nowrap;\n }\n thead th:first-child { border-radius: 6px 0 0 0; }\n thead th:last-child { border-radius: 0 6px 0 0; }\n tbody tr:nth-child(odd) { background: var(--white); }\n tbody tr:nth-child(even) { background: var(--light); }\n tbody td {\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n vertical-align: top;\n font-size: 12.5px;\n color: var(--text);\n }\n\n /* \u2500\u2500 Empty state \u2500\u2500 */\n .empty-state {\n color: var(--muted);\n font-style: italic;\n font-size: 13px;\n padding: 20px 0 4px;\n }\n\n /* \u2500\u2500 Notes footer \u2500\u2500 */\n .notes-footer {\n margin-top: 24px;\n padding-top: 14px;\n border-top: 1px solid var(--cyan);\n font-size: 11px;\n color: var(--muted);\n font-style: italic;\n text-align: center;\n }\n\n /* \u2500\u2500 Scrollbar \u2500\u2500 */\n ::-webkit-scrollbar { width: 6px; height: 6px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }\n</style>\n</head>\n<body>\n\n<!-- SIDEBAR -->\n<aside class=\"sidebar\">\n <div class=\"sidebar-logo\">\n <div class=\"brand\">XYZ <span>Technologies</span></div>\n <div class=\"file-name\">${esc(fileName)}</div>\n </div>\n <nav class=\"sidebar-nav\">\n <div class=\"nav-label\">Contents</div>\n ${tocItems.map(([n,t]) => `\n <a class=\"nav-item\" href=\"#sec${n}\">\n <span class=\"num\">${n}</span>${t}\n </a>`).join('')}\n </nav>\n <div class=\"sidebar-footer\">\n v1.0 \u00b7 Internal<br>Generated: ${today}\n </div>\n</aside>\n\n<!-- MAIN -->\n<div class=\"main\">\n\n <!-- TOP BAR -->\n <div class=\"topbar\">\n <div class=\"topbar-title\">API Documentation — <span style=\"font-family:'IBM Plex Mono',monospace;font-weight:400\">${esc(fileName)}</span></div>\n <div style=\"display:flex;gap:8px;align-items:center\">\n <span class=\"badge\">C Embedded</span>\n <span class=\"topbar-meta\">${today}</span>\n </div>\n </div>\n\n <!-- HERO -->\n <div class=\"hero\">\n <div class=\"hero-eyebrow\">XYZ Technologies \u00b7 Header File Documentation</div>\n <div class=\"hero-title\">${esc(baseName.toUpperCase())}</div>\n <div class=\"hero-accent\"></div>\n <div class=\"hero-subtitle\">C Embedded Systems Interface Reference</div>\n <div class=\"hero-meta\">\n <div class=\"hero-meta-item\"><span class=\"meta-key\">Source</span><span class=\"meta-val\">${esc(fileName)}</span></div>\n <div class=\"hero-meta-item\"><span class=\"meta-key\">Prepared by</span><span class=\"meta-val\">XYZ Technologies</span></div>\n <div class=\"hero-meta-item\"><span class=\"meta-key\">Date</span><span class=\"meta-val\">${today}</span></div>\n <div class=\"hero-meta-item\"><span class=\"meta-key\">Version</span><span class=\"meta-val\">1.0</span></div>\n <div class=\"hero-meta-item\"><span class=\"meta-key\">Classification</span><span class=\"meta-val\">Internal</span></div>\n </div>\n </div>\n\n <!-- CONTENT -->\n <div class=\"content\">\n\n <!-- 1. OVERVIEW -->\n <div class=\"section\" id=\"sec1\">\n <div class=\"section-header\">\n <span class=\"section-num\">01</span>\n <span class=\"section-title\">Overview</span>\n </div>\n <div class=\"card\">\n <p>${esc(doc.overview || 'No overview available.')}</p>\n </div>\n </div>\n\n <!-- 2. FUNCTIONS -->\n <div class=\"section\" id=\"sec2\">\n <div class=\"section-header\">\n <span class=\"section-num\">02</span>\n <span class=\"section-title\">Functions</span>\n </div>\n ${fnHtml}\n </div>\n\n <!-- 3. ENUMERATORS -->\n <div class=\"section\" id=\"sec3\">\n <div class=\"section-header\">\n <span class=\"section-num\">03</span>\n <span class=\"section-title\">Enumerators</span>\n </div>\n ${enumHtml}\n </div>\n\n <!-- 4. DATA TYPES & STRUCTURES -->\n <div class=\"section\" id=\"sec4\">\n <div class=\"section-header\">\n <span class=\"section-num\">04</span>\n <span class=\"section-title\">Data Types & Structures</span>\n </div>\n ${dtHtml}\n </div>\n\n <!-- 5. CONSTANTS -->\n <div class=\"section\" id=\"sec5\">\n <div class=\"section-header\">\n <span class=\"section-num\">05</span>\n <span class=\"section-title\">Constants</span>\n </div>\n ${constHtml}\n </div>\n\n <!-- 6. NOTES -->\n <div class=\"section\" id=\"sec6\">\n <div class=\"section-header\">\n <span class=\"section-num\">06</span>\n <span class=\"section-title\">Notes</span>\n </div>\n <div class=\"card\">\n <p>${esc(doc.notes || 'No additional notes.')}</p>\n <div class=\"notes-footer\">This document was automatically generated by XYZ Technologies.</div>\n </div>\n </div>\n\n </div><!-- /content -->\n</div><!-- /main -->\n\n</body>\n</html>`;\n\n// Convert HTML string to base64 binary for Google Drive upload\nconst b64 = Buffer.from(html, 'utf8').toString('base64');\n\nreturn [{\n json: {\n htmlName,\n fileName,\n timestamp: today\n },\n binary: {\n data: {\n data: b64,\n mimeType: 'text/html',\n fileName: htmlName,\n fileExtension: 'html'\n }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "0b63f327-e409-4fe9-8716-bf4d53697441",
"name": "Save PDF to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
928,
544
],
"parameters": {
"name": "={{ $json.pdfName }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "MyDrive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "id",
"value": "YOUR_OUTPUT_FOLDER_ID"
}
},
"typeVersion": 3
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"connections": {
"Download file": {
"main": [
[
{
"node": "Extract from File2",
"type": "main",
"index": 0
}
]
]
},
"Generate HTML": {
"main": [
[
{
"node": "Save PDF to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Build email body": {
"main": [
[
{
"node": "Send completion email",
"type": "main",
"index": 0
}
]
]
},
"Extract from File2": {
"main": [
[
{
"node": "Extract structured docs (GPT-4o)",
"type": "main",
"index": 0
}
]
]
},
"Parse JSON response": {
"main": [
[
{
"node": "Generate HTML",
"type": "main",
"index": 0
}
]
]
},
"Filter .h files only": {
"main": [
[
{
"node": "Process one file at a time",
"type": "main",
"index": 0
}
]
]
},
"Save PDF to Google Drive": {
"main": [
[
{
"node": "Process one file at a time",
"type": "main",
"index": 0
}
]
]
},
"Get all files from folder": {
"main": [
[
{
"node": "Filter .h files only",
"type": "main",
"index": 0
}
]
]
},
"Process one file at a time": {
"main": [
[
{
"node": "Build email body",
"type": "main",
"index": 0
}
],
[
{
"node": "Download file",
"type": "main",
"index": 0
}
]
]
},
"Extract structured docs (GPT-4o)": {
"main": [
[
{
"node": "Parse JSON response",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Get all files from folder",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n workflow automatically generates professional API documentation from C header () files using AI.
Source: https://n8n.io/workflows/14093/ — 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.
What it is An automated LinkedIn content system that takes a simple form (idea + optional file), generates LinkedIn posts with OpenAI, stores them in Notion, builds Google Slides carousels, and auto-p
An n8n-based automation that generates client proposals from a form, lets you review everything in one place, and sends the proposal only when you approve it.
Overview
💥 Automate YouTube thumbnail creation from video links -vide. Uses telegramTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 25 nodes.
💥 Automate YouTube thumbnail creation from video links -vide. Uses telegramTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 25 nodes.