Introduction
Keeping technical documentation in sync with your codebase is a perpetual headache. You update the code, documentation falls behind. You refactor an API endpoint, nobody notices the old examples are broken. By the time you realise, users are confused and your developer experience suffers.
The traditional approach involves manual effort: developers write code, then someone separately writes docs, then someone else maintains them. It's inefficient, error-prone, and doesn't scale. What if your documentation could update itself whenever your code changes?
This workflow combines three tools to automate technical documentation generation from your source code. You point it at your codebase, it extracts function signatures and comments, generates interactive documentation with embedded code examples, and deploys it to a live site. All triggered automatically, with zero manual handoff between tools.
The Automated Workflow
We'll build a system where code commits trigger documentation generation, which then publishes to a live site. The workflow uses Windsurf to analyse your codebase, v0 to design the documentation interface, and Mintlify to host and render it all.
For orchestration, we'll use n8n because it offers the best balance of affordability and control for this particular workflow. You can substitute Make or Zapier if you prefer, but n8n's ability to handle complex data transformations between tools makes it ideal here.
Architecture Overview
The flow works like this:
- You push code to GitHub (or your repository).
- A webhook triggers n8n.
- n8n calls Windsurf to analyse your codebase and extract documentation-ready content.
- Windsurf generates structured data: function names, parameters, return types, and docstring content.
- n8n transforms that data into Mintlify's configuration format.
- v0 (if you need UI customisation) generates styled components for the documentation layout.
- n8n sends the final output to Mintlify via API, which deploys it.
The entire process takes 2-5 minutes, depending on codebase size.
Prerequisites and Setup
Before building the workflow, you need:
-
A GitHub repository with well-commented code.
-
A Mintlify account (free tier works for small projects).
-
Windsurf API access (available through Claude's API).
-
An n8n instance (cloud or self-hosted).
-
Optional: v0 account for UI generation.
First, generate your API keys. Get your Claude API key from the console, and set up a personal access token in GitHub with repo and read:code permissions.
Step 1:
Set Up the Webhook Trigger
In n8n, create a new workflow and add a Webhook node. Configure it to listen for GitHub pushes:
POST /webhook/github-documentation
Headers:
X-GitHub-Event: push
X-GitHub-Delivery: [unique-id]
X-Hub-Signature-256: sha256=[signature]
In your GitHub repository settings, go to Settings > Webhooks and create a new webhook pointing to your n8n webhook URL. Select "Push events" as the trigger.
The webhook payload from GitHub includes the repository URL, branch name, and commit details. You'll use these to fetch the latest code.
Step 2:
Fetch Code from Repository
Add an HTTP Request node to your n8n workflow. This node retrieves the files from your repository:
{
"method": "GET",
"url": "https://api.github.com/repos/[owner]/[repo]/contents",
"headers": {
"Authorization": "token [YOUR_GITHUB_TOKEN]",
"Accept": "application/vnd.github.v3.raw"
},
"qs": {
"ref": "{{ $node['Webhook'].json.ref }}"
}
}
This returns a list of files in your repository. You'll need to filter for source files (.ts, .py, .js, etc.) and fetch their contents in a subsequent request. Use a Loop node to iterate over each file.
For each file, make another HTTP request to get the raw content:
{
"method": "GET",
"url": "https://raw.githubusercontent.com/[owner]/[repo]/{{ $node['Loop'].json.ref }}/{{ $node['Loop'].json.path }}",
"headers": {
"Authorization": "token [YOUR_GITHUB_TOKEN]"
}
}
Store the file contents in a JavaScript object. You now have all your source code ready for analysis.
Step 3:
Analyse Code with Windsurf
Add a Function node (or HTTP request if using Claude API directly) to call Windsurf's analysis capability. Windsurf understands code context better than plain prompting, so we'll use Claude's API with the claude-3-5-sonnet model, which has excellent code understanding.
{
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"headers": {
"x-api-key": "{{ $secrets.CLAUDE_API_KEY }}",
"content-type": "application/json",
"anthropic-version": "2023-06-01"
},
"body": {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Analyse this codebase and extract documentation-ready content. For each function or class, provide: name, description (from docstring), parameters with types, return type, and usage example. Format as JSON. Here is the code:\n\n{{ $node['HTTP Request'].json }}"
}
]
}
}
Claude will return structured JSON with function signatures, docstrings, and type information. Here's what the output looks like:
{
"functions": [
{
"name": "calculate_roi",
"description": "Calculates return on investment given initial and final values",
"parameters": [
{
"name": "initial_value",
"type": "float",
"description": "The starting investment amount"
},
{
"name": "final_value",
"type": "float",
"description": "The ending investment amount"
}
],
"return_type": "float",
"return_description": "ROI as a percentage",
"example": "result = calculate_roi(1000, 1500)"
}
]
}
Store this output in n8n's data for the next step.
Step 4:
Transform to Mintlify Format
Mintlify uses a specific directory structure and YAML/MDX format for documentation. Create a Function node that transforms the extracted data into Mintlify's format:
const extractedData = $node['HTTP Request'].json;
const mintlifyConfig = {
name: "API Documentation",
logo: {
light: "/logo/light.png",
dark: "/logo/dark.png"
},
favicon: "/favicon.png",
colors: {
primary: "#0D9373",
light: "#07C983",
dark: "#0D9373"
},
topbarLinks: [
{
label: "Support",
href: "https://support.example.com"
}
],
tabs: [
{
name: "API",
url: "api-reference"
}
],
navigation: [
{
group: "Functions",
pages: extractedData.functions.map(func => ({
name: func.name,
url: `api-reference/${func.name}`
}))
}
]
};
// Generate MDX content for each function
const docPages = extractedData.functions.map(func => {
const params = func.parameters
.map(p => `- **${p.name}** (${p.type}): ${p.description}`)
.join('\n');
return {
filename: `${func.name}.mdx`,
content: `---
title: "${func.name}"
description: "${func.description}"
## Overview
${func.description}
## Parameters
${params}
## Returns
**Type:** ${func.return_type}
${func.return_description}
## Example
\`\`\`python
${func.example}
\`\`\`
`
};
});
return { mintlifyConfig, docPages };
This function creates both the Mintlify configuration file and individual documentation pages for each function.
Step 5:
Generate UI with v0 (Optional)
If your documentation needs custom styling or interactive components, add an HTTP request node that calls v0 to generate React components:
{
"method": "POST",
"url": "https://api.v0.dev/generate",
"headers": {
"Authorization": "Bearer {{ $secrets.V0_API_KEY }}",
"Content-Type": "application/json"
},
"body": {
"prompt": "Generate a React component for displaying API documentation. Include a sidebar for navigation, a code block for examples, and a parameter table. Use Tailwind CSS. The documentation is for: {{ $node['Function'].json.docPages[0].content }}",
"model": "v0"
}
}
v0 returns JSX code that you can integrate into your Mintlify configuration if needed. Most of the time, Mintlify's built-in styling is sufficient, so this step is optional.
Step 6:
Deploy to Mintlify
Finally, commit the generated documentation to a repository (or directly call Mintlify's API) to trigger deployment:
{
"method": "POST",
"url": "https://api.mintlify.com/v1/docs/deploy",
"headers": {
"Authorization": "Bearer {{ $secrets.MINTLIFY_API_KEY }}",
"Content-Type": "application/json"
},
"body": {
"repo": "[owner]/[repo]-docs",
"branch": "main",
"files": [
{
"path": "mint.json",
"content": JSON.stringify($node['Function'].json.mintlifyConfig)
},
...$node['Function'].json.docPages.map(page => ({
"path": `docs/api/${page.filename}`,
"content": page.content
}))
]
}
}
If Mintlify doesn't have a direct API for this, commit the files to a GitHub repository that's connected to Mintlify, and Mintlify will auto-deploy. Use the GitHub API to create or update files:
{
"method": "PUT",
"url": "https://api.github.com/repos/[owner]/[repo]-docs/contents/mint.json",
"headers": {
"Authorization": "token {{ $secrets.GITHUB_TOKEN }}",
"Content-Type": "application/json"
},
"body": {
"message": "Auto-update documentation from code changes",
"content": Buffer.from(JSON.stringify($node['Function'].json.mintlifyConfig)).toString('base64'),
"branch": "main"
}
}
Add a 30-second delay after pushing files, then add a final HTTP request to trigger Mintlify's build if it doesn't auto-trigger.
Complete n8n Workflow Structure
Here's how your n8n workflow nodes should connect:
- GitHub Webhook (trigger)
- HTTP Request: Fetch repository file list
- Loop: Iterate over files
- HTTP Request: Get file contents (inside loop)
- Function: Aggregate code files
- HTTP Request: Call Claude API via Windsurf
- Function: Transform to Mintlify format
- Loop: Create documentation pages
- HTTP Request: Commit to docs repository
- Delay: Wait 30 seconds
- HTTP Request: Trigger Mintlify rebuild
Each node passes data to the next via {{ $node['NodeName'].json }} references.
The Manual Alternative
If you prefer more control over the documentation generation process, you can run each step manually:
- Clone your repository locally.
- Use Claude in Windsurf directly to analyse your code and extract documentation-ready content.
- Manually write or refine the Mintlify configuration and MDX files.
- Run
mintlify devlocally to preview changes. - Commit to your docs repository and deploy via Mintlify's dashboard.
This approach takes longer but gives you complete control over documentation tone, examples, and organisation. It's suitable if your documentation requires significant custom explanation or if you update your API infrequently.
You can also use a hybrid approach: automate the initial extraction with Windsurf, then manually review and refine the output before deploying.
Pro Tips
1. Handle Rate Limits
GitHub API allows 60 requests per hour unauthenticated, 5,000 with authentication. Claude API has variable rate limits depending on your plan. In your n8n workflow, add a delay between API calls and implement retry logic:
{
"retry": {
"maxRetries": 3,
"waitBetweenRetries": 5000,
"backoffMultiplier": 2
}
}
This exponentially backs off if you hit rate limits.
2. Filter Relevant Files
Not every file in your repository needs documentation. Before analysing, filter for specific file types and exclude tests, build outputs, and vendor directories:
const sourceFiles = fileList.filter(file =>
(file.name.endsWith('.ts') ||
file.name.endsWith('.py') ||
file.name.endsWith('.js')) &&
!file.path.includes('node_modules') &&
!file.path.includes('tests') &&
!file.path.includes('dist')
);
This reduces API calls and improves analysis quality.
3. Version Your Documentation
Keep documentation versions aligned with code releases. In your Mintlify config, add version handling:
{
"versions": [
{
"name": "v2",
"url": "https://docs.example.com/v2"
},
{
"name": "v1",
"url": "https://docs.example.com/v1"
}
]
}
This prevents users on older versions from seeing incompatible API documentation.
4. Monitor Generation Success
Add error handling to your n8n workflow. If Claude returns incomplete data or the deployment fails, send a notification:
{
"method": "POST",
"url": "https://hooks.slack.com/services/[YOUR_WEBHOOK]",
"body": {
"text": "Documentation generation failed: {{ $node['HTTP Request'].json.error }}"
}
}
This ensures you know immediately if something breaks rather than discovering it when users complain.
5. Cache Extracted Data
If you're processing a large codebase, store the extracted documentation in a database or file storage between runs. Only re-analyse files that have changed:
{
"method": "POST",
"url": "https://your-cache-service.com/docs",
"body": {
"repo": "{{ $node['Webhook'].json.repository.name }}",
"commit": "{{ $node['Webhook'].json.after }}",
"data": "{{ $node['Function'].json }}"
}
}
This reduces API calls and speeds up the workflow significantly.
Cost Breakdown
| Tool | Plan Needed | Monthly Cost | Notes |
|---|---|---|---|
| Windsurf / Claude API | Pay-as-you-go | £1-5 | Costs depend on codebase size and frequency. A small codebase with daily runs costs ~£2/month. |
| Mintlify | Free or Pro | £0-20 | Free tier covers most projects. Pro (£20/month) adds analytics and custom domains. |
| v0 | Free or Pro | £0-15 | Free tier generates basic components. Pro (£15/month) for advanced features. Only needed if you want UI customisation. |
| n8n | Cloud or Self-hosted | £0-50+ | Cloud starter (£10/month) for small workflows. Self-hosted is free but requires your own server. |
| GitHub API | Included | £0 | You likely already have GitHub; API access is free with authentication. |
| Total | £1-90/month | Most teams pay £5-25/month using free tiers and minimal API usage. |
The smallest viable setup costs nothing if you self-host n8n and use free Mintlify plus Claude API's pay-as-you-go model. Scale the costs up if you need premium features or higher API volumes.