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.
This workflow watches a Notion CRM for leads ready to contact, checks Gmail to avoid duplicate outreach, then sends or drafts an email with Google Drive PDF attachments and updates the lead status in
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
Transcript Audio To Text. Uses googleDocs, googleDrive, openAi, gmail. Event-driven trigger; 31 nodes.
AI Expense Tracker. Uses gmailTrigger, openAi, googleDrive, googleSheets. Event-driven trigger; 31 nodes.
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.