Skip to content
Get started

Build Your First Tabstack App

In this tutorial, you'll build a command-line tool that converts any documentation website into an AI-friendly AGENTS.md file. This file helps AI assistants understand and work with the documented API or tool.

A Node.js application that:

  1. Takes a documentation URL as input
  2. Extracts all documentation pages from the site
  3. Reads and processes the content
  4. Generates a comprehensive AGENTS.md file summarizing the documentation
  • How to use the Tabstack API to convert web pages to markdown
  • How to extract and process multiple documentation pages
  • How to use AI-powered transformations to generate structured documentation
  • Node.js 20+ installed
  • A Tabstack API key (get one here)
  • Basic command-line knowledge
  • 20 minutes of your time
Terminal window
mkdir doc-parser
cd doc-parser
npm init -y
Terminal window
npm install axios dotenv
  • axios: For making HTTP requests to the Tabstack API
  • dotenv: For managing your API key securely

Create a .env file in your project root:

Terminal window
TABSTACK_API_KEY=your_api_key_here

Replace your_api_key_here with your actual API key from the Tabstack Console.

:::warning Never commit your .env file to version control! Add it to .gitignore:

Terminal window
echo ".env" >> .gitignore

:::

Here’s how our application will work:

┌─────────────────┐
│ Documentation │
│ Website URL │
└────────┬────────┘
┌─────────────────────────┐
│ 1. Fetch Main Page │ ← /v1/extract/markdown endpoint
│ (Convert to MD) │
└────────┬────────────────┘
┌─────────────────────────┐
│ 2. Extract Doc Links │ ← Parse markdown
│ from Content │
└────────┬────────────────┘
┌─────────────────────────┐
│ 3. Fetch All Doc │ ← /v1/extract/markdown endpoint
│ Pages (Convert) │ (multiple calls)
└────────┬────────────────┘
┌─────────────────────────┐
│ 4. Generate AGENTS.md │ ← /v1/generate/json endpoint
│ with AI │
└────────┬────────────────┘
┌─────────────────────────┐
│ AGENTS.md File │
│ Ready for AI Use! │
└─────────────────────────┘

Create index.js:

// Import required libraries
require("dotenv").config();
const axios = require("axios");
const fs = require("fs").promises;
// Configuration
const TABSTACK_API_BASE = "https://api.tabstack.ai";
const API_KEY = process.env.TABSTACK_API_KEY;
// Check if API key is set
if (!API_KEY) {
console.error(
"❌ Error: TABSTACK_API_KEY not found in environment variables"
);
console.error("Please create a .env file with your API key");
process.exit(1);
}
// Main function
async function main() {
// Get URL from command line arguments
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// We'll add our implementation here
console.log("✅ Done!");
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}
// Run the application
main();

Add this helper function before the main() function:

/**
* Fetch a URL and convert it to markdown using Tabstack API
* @param {string} url - The URL to fetch
* @returns {Promise<{url: string, content: string}>} The markdown content
*/
async function fetchAsMarkdown(url) {
console.log(`📥 Fetching: ${url}`);
try {
const response = await axios.post(
`${TABSTACK_API_BASE}/v1/extract/markdown`,
{ url },
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
}
);
return response.data;
} catch (error) {
if (error.response) {
throw new Error(
`API Error: ${error.response.status} - ${error.response.data.error || "Unknown error"}`
);
}
throw error;
}
}

Now update the main() function to fetch the main page:

async function main() {
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// Step 1: Fetch the main documentation page
console.log("Step 1: Fetching main documentation page...");
const mainPage = await fetchAsMarkdown(url);
console.log(`✅ Fetched ${mainPage.content.length} characters\n`);
console.log("✅ Done!");
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}

Add this function to extract links from markdown content:

/**
* Extract documentation links from markdown content
* @param {string} markdown - The markdown content
* @param {string} baseUrl - The base URL for resolving relative links
* @returns {string[]} Array of unique documentation URLs
*/
function extractDocLinks(markdown, baseUrl) {
console.log("🔍 Extracting documentation links...");
// Regular expression to match markdown links: [text](url)
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const links = new Set();
let match;
while ((match = linkRegex.exec(markdown)) !== null) {
let link = match[2];
// Skip anchors, mailto, and external protocol links
if (
link.startsWith("#") ||
link.startsWith("mailto:") ||
link.startsWith("tel:")
) {
continue;
}
// Convert relative URLs to absolute
try {
const absoluteUrl = new URL(link, baseUrl).href;
// Only include links from the same domain
const baseDomain = new URL(baseUrl).hostname;
const linkDomain = new URL(absoluteUrl).hostname;
if (linkDomain === baseDomain) {
links.add(absoluteUrl);
}
} catch (e) {
// Skip invalid URLs
continue;
}
}
const uniqueLinks = Array.from(links);
console.log(`✅ Found ${uniqueLinks.length} documentation links\n`);
return uniqueLinks;
}

Update main() to extract links:

async function main() {
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// Step 1: Fetch the main documentation page
console.log("Step 1: Fetching main documentation page...");
const mainPage = await fetchAsMarkdown(url);
console.log(`✅ Fetched ${mainPage.content.length} characters\n`);
// Step 2: Extract documentation links
console.log("Step 2: Extracting documentation links...");
const docLinks = extractDocLinks(mainPage.content, url);
// Limit to first 10 pages to avoid rate limits (remove this for production)
const linksToFetch = docLinks.slice(0, 10);
console.log(`📚 Will fetch ${linksToFetch.length} documentation pages\n`);
console.log("✅ Done!");
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}

Add a function to fetch multiple pages with a delay to respect rate limits:

/**
* Fetch multiple URLs as markdown with rate limiting
* @param {string[]} urls - Array of URLs to fetch
* @returns {Promise<Array<{url: string, content: string}>>} Array of markdown content
*/
async function fetchMultipleAsMarkdown(urls) {
console.log("📥 Fetching documentation pages...");
const results = [];
for (let i = 0; i < urls.length; i++) {
try {
const result = await fetchAsMarkdown(urls[i]);
results.push(result);
console.log(` ✅ ${i + 1}/${urls.length} complete`);
// Add a small delay between requests to be respectful
if (i < urls.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
} catch (error) {
console.log(` ⚠️ Failed to fetch ${urls[i]}: ${error.message}`);
// Continue with other URLs
}
}
console.log(
`✅ Successfully fetched ${results.length}/${urls.length} pages\n`
);
return results;
}

Update main() to fetch all documentation pages:

async function main() {
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// Step 1: Fetch the main documentation page
console.log("Step 1: Fetching main documentation page...");
const mainPage = await fetchAsMarkdown(url);
console.log(`✅ Fetched ${mainPage.content.length} characters\n`);
// Step 2: Extract documentation links
console.log("Step 2: Extracting documentation links...");
const docLinks = extractDocLinks(mainPage.content, url);
// Limit to first 10 pages to avoid rate limits
const linksToFetch = docLinks.slice(0, 10);
console.log(`📚 Will fetch ${linksToFetch.length} documentation pages\n`);
// Step 3: Fetch all documentation pages
console.log("Step 3: Fetching all documentation pages...");
const allPages = await fetchMultipleAsMarkdown([url, ...linksToFetch]);
console.log("✅ Done!");
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}

Now for the magic! We’ll use the /transform endpoint to analyze all the documentation and generate a structured AGENTS.md file:

/**
* Generate AGENTS.md content using AI transformation
* @param {string} url - The base documentation URL
* @param {Array<{url: string, content: string}>} pages - All fetched pages
* @returns {Promise<string>} The generated AGENTS.md content
*/
async function generateAgentsMd(url, pages) {
console.log("🤖 Generating AGENTS.md with AI...");
// Combine all markdown content
const combinedContent = pages
.map((page) => {
return `# Source: ${page.url}\n\n${page.content}\n\n---\n\n`;
})
.join("");
// Create a temporary combined markdown file URL
// For the transform endpoint, we'll use the original URL
// and pass instructions for comprehensive analysis
const instructions = `
Analyze this documentation comprehensively and create an AGENTS.md file that helps AI assistants understand and work with this API/tool.
The AGENTS.md should include:
1. **Project Overview**: Brief description of what this project/API does
2. **Key Concepts**: Important concepts, terminology, and architecture
3. **API Endpoints**: Summary of available endpoints (if applicable) with methods and purposes
4. **Authentication**: How to authenticate (if applicable)
5. **Common Use Cases**: Typical scenarios and workflows
6. **Code Examples**: Key usage patterns with code snippets from the docs
7. **Important Notes**: Rate limits, best practices, gotchas, limitations
Format the output as clear, well-structured markdown that an AI agent can easily parse and understand.
Focus on actionable information that helps an AI assistant answer questions and help users work with this tool.
`.trim();
// Define the schema for the output
const schema = {
type: "object",
properties: {
content: {
type: "string",
description: "The complete AGENTS.md file content in markdown format",
},
},
required: ["content"],
additionalProperties: false,
};
try {
const response = await axios.post(
`${TABSTACK_API_BASE}/v1/generate/json`,
{
url: url,
instructions: instructions,
json_schema: schema,
},
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
}
);
console.log("✅ AGENTS.md generated successfully\n");
return response.data.content;
} catch (error) {
if (error.response) {
throw new Error(
`API Error: ${error.response.status} - ${error.response.data.error || "Unknown error"}`
);
}
throw error;
}
}

Add a function to save the AGENTS.md file:

/**
* Save content to a file
* @param {string} filename - The filename to save to
* @param {string} content - The content to save
*/
async function saveToFile(filename, content) {
console.log(`💾 Saving to ${filename}...`);
await fs.writeFile(filename, content, "utf8");
console.log(`✅ Saved successfully\n`);
}

Now let’s tie it all together in the main() function:

async function main() {
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// Step 1: Fetch the main documentation page
console.log("Step 1: Fetching main documentation page...");
const mainPage = await fetchAsMarkdown(url);
console.log(`✅ Fetched ${mainPage.content.length} characters\n`);
// Step 2: Extract documentation links
console.log("Step 2: Extracting documentation links...");
const docLinks = extractDocLinks(mainPage.content, url);
// Limit to first 10 pages to avoid rate limits
const linksToFetch = docLinks.slice(0, 10);
console.log(`📚 Will fetch ${linksToFetch.length} documentation pages\n`);
// Step 3: Fetch all documentation pages
console.log("Step 3: Fetching all documentation pages...");
const allPages = await fetchMultipleAsMarkdown([url, ...linksToFetch]);
// Step 4: Generate AGENTS.md
console.log("Step 4: Generating AGENTS.md...");
const agentsMd = await generateAgentsMd(url, allPages);
// Step 5: Save to file
await saveToFile("AGENTS.md", agentsMd);
console.log("✅ Done! Your AGENTS.md file is ready.");
console.log(`📝 Generated from ${allPages.length} documentation pages`);
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}

Now you can run your documentation parser!

Terminal window
node index.js https://docs.example.com
🚀 Starting documentation parser...
📄 Target URL: https://docs.stripe.com/api
Step 1: Fetching main documentation page...
📥 Fetching: https://docs.stripe.com/api
✅ Fetched 45231 characters
Step 2: Extracting documentation links...
🔍 Extracting documentation links...
✅ Found 87 documentation links
📚 Will fetch 10 documentation pages
Step 3: Fetching all documentation pages...
📥 Fetching documentation pages...
✅ 1/10 complete
✅ 2/10 complete
...
✅ Successfully fetched 10/10 pages
Step 4: Generating AGENTS.md...
🤖 Generating AGENTS.md with AI...
✅ AGENTS.md generated successfully
💾 Saving to AGENTS.md...
✅ Saved successfully
✅ Done! Your AGENTS.md file is ready.
📝 Generated from 10 documentation pages

Your generated file might look like this:

# Stripe API - AI Agent Documentation
## Project Overview
Stripe is a payment processing platform that provides APIs for accepting payments,
managing subscriptions, and handling financial transactions...
## Key Concepts
- **Payment Intents**: Represents the lifecycle of a customer payment
- **Customers**: Represents your customers in Stripe
- **Subscriptions**: Recurring payment schedules...
## API Endpoints
### Payments
- `POST /v1/payment_intents` - Create a payment intent
- `GET /v1/payment_intents/:id` - Retrieve payment details
- `POST /v1/payment_intents/:id/confirm` - Confirm payment
### Customers
- `POST /v1/customers` - Create a new customer
- `GET /v1/customers/:id` - Retrieve customer details...
[... more content ...]

Try it with different documentation sites:

Terminal window
# Stripe API docs
node index.js https://docs.stripe.com/api
# Tabstack API docs (meta!)
node index.js https://docs.tabstack.ai
# GitHub REST API docs
node index.js https://docs.github.com/en/rest

Here’s the complete index.js file for reference:

// Import required libraries
require("dotenv").config();
const axios = require("axios");
const fs = require("fs").promises;
// Configuration
const TABSTACK_API_BASE = "https://api.tabstack.ai";
const API_KEY = process.env.TABSTACK_API_KEY;
// Check if API key is set
if (!API_KEY) {
console.error(
"❌ Error: TABSTACK_API_KEY not found in environment variables"
);
console.error("Please create a .env file with your API key");
process.exit(1);
}
/**
* Fetch a URL and convert it to markdown using Tabstack API
*/
async function fetchAsMarkdown(url) {
console.log(`📥 Fetching: ${url}`);
try {
const response = await axios.post(
`${TABSTACK_API_BASE}/v1/extract/markdown`,
{ url },
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
}
);
return response.data;
} catch (error) {
if (error.response) {
throw new Error(
`API Error: ${error.response.status} - ${error.response.data.error || "Unknown error"}`
);
}
throw error;
}
}
/**
* Extract documentation links from markdown content
*/
function extractDocLinks(markdown, baseUrl) {
console.log("🔍 Extracting documentation links...");
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const links = new Set();
let match;
while ((match = linkRegex.exec(markdown)) !== null) {
let link = match[2];
if (
link.startsWith("#") ||
link.startsWith("mailto:") ||
link.startsWith("tel:")
) {
continue;
}
try {
const absoluteUrl = new URL(link, baseUrl).href;
const baseDomain = new URL(baseUrl).hostname;
const linkDomain = new URL(absoluteUrl).hostname;
if (linkDomain === baseDomain) {
links.add(absoluteUrl);
}
} catch (e) {
continue;
}
}
const uniqueLinks = Array.from(links);
console.log(`✅ Found ${uniqueLinks.length} documentation links\n`);
return uniqueLinks;
}
/**
* Fetch multiple URLs as markdown with rate limiting
*/
async function fetchMultipleAsMarkdown(urls) {
console.log("📥 Fetching documentation pages...");
const results = [];
for (let i = 0; i < urls.length; i++) {
try {
const result = await fetchAsMarkdown(urls[i]);
results.push(result);
console.log(` ✅ ${i + 1}/${urls.length} complete`);
if (i < urls.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
} catch (error) {
console.log(` ⚠️ Failed to fetch ${urls[i]}: ${error.message}`);
}
}
console.log(
`✅ Successfully fetched ${results.length}/${urls.length} pages\n`
);
return results;
}
/**
* Generate AGENTS.md content using AI transformation
*/
async function generateAgentsMd(url, pages) {
console.log("🤖 Generating AGENTS.md with AI...");
const instructions = `
Analyze this documentation comprehensively and create an AGENTS.md file that helps AI assistants understand and work with this API/tool.
The AGENTS.md should include:
1. **Project Overview**: Brief description of what this project/API does
2. **Key Concepts**: Important concepts, terminology, and architecture
3. **API Endpoints**: Summary of available endpoints (if applicable) with methods and purposes
4. **Authentication**: How to authenticate (if applicable)
5. **Common Use Cases**: Typical scenarios and workflows
6. **Code Examples**: Key usage patterns with code snippets from the docs
7. **Important Notes**: Rate limits, best practices, gotchas, limitations
Format the output as clear, well-structured markdown that an AI agent can easily parse and understand.
Focus on actionable information that helps an AI assistant answer questions and help users work with this tool.
`.trim();
const schema = {
type: "object",
properties: {
content: {
type: "string",
description: "The complete AGENTS.md file content in markdown format",
},
},
required: ["content"],
additionalProperties: false,
};
try {
const response = await axios.post(
`${TABSTACK_API_BASE}/v1/generate/json`,
{
url: url,
instructions: instructions,
json_schema: schema,
},
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
}
);
console.log("✅ AGENTS.md generated successfully\n");
return response.data.content;
} catch (error) {
if (error.response) {
throw new Error(
`API Error: ${error.response.status} - ${error.response.data.error || "Unknown error"}`
);
}
throw error;
}
}
/**
* Save content to a file
*/
async function saveToFile(filename, content) {
console.log(`💾 Saving to ${filename}...`);
await fs.writeFile(filename, content, "utf8");
console.log(`✅ Saved successfully\n`);
}
/**
* Main function
*/
async function main() {
const url = process.argv[2];
if (!url) {
console.error("❌ Error: Please provide a documentation URL");
console.error("Usage: node index.js <documentation-url>");
process.exit(1);
}
console.log("🚀 Starting documentation parser...");
console.log(`📄 Target URL: ${url}\n`);
try {
// Step 1: Fetch the main documentation page
console.log("Step 1: Fetching main documentation page...");
const mainPage = await fetchAsMarkdown(url);
console.log(`✅ Fetched ${mainPage.content.length} characters\n`);
// Step 2: Extract documentation links
console.log("Step 2: Extracting documentation links...");
const docLinks = extractDocLinks(mainPage.content, url);
// Limit to first 10 pages to avoid rate limits
const linksToFetch = docLinks.slice(0, 10);
console.log(`📚 Will fetch ${linksToFetch.length} documentation pages\n`);
// Step 3: Fetch all documentation pages
console.log("Step 3: Fetching all documentation pages...");
const allPages = await fetchMultipleAsMarkdown([url, ...linksToFetch]);
// Step 4: Generate AGENTS.md
console.log("Step 4: Generating AGENTS.md...");
const agentsMd = await generateAgentsMd(url, allPages);
// Step 5: Save to file
await saveToFile("AGENTS.md", agentsMd);
console.log("✅ Done! Your AGENTS.md file is ready.");
console.log(`📝 Generated from ${allPages.length} documentation pages`);
} catch (error) {
console.error("❌ Error:", error.message);
process.exit(1);
}
}
// Run the application
main();

Ready to explore more?