Ever wanted to quickly check a website's SEO health without paying for expensive tools like Ahrefs or Semrush? You can build your own basic SEO auditor in under 100 lines of JavaScript using free APIs.
In this tutorial, we'll build a CLI tool that:
- Scrapes any page for meta tags, headings, and links
- Checks DNS records and hosting info
- Takes a visual screenshot
- Generates a clean audit report
What You'll Need
- Node.js 18+
- A free API key from Agent Gateway (200 free credits, no card required)
Step 1: Get Your API Key
curl -X POST https://api.frostbyte.world/api/keys/create
Save the key from the response. Each API call costs 1 credit, so one full audit uses ~4 credits.
Step 2: The SEO Audit Script
Create seo-audit.js:
const API_KEY = process.env.FROSTBYTE_KEY || 'your-key-here';
const BASE = 'https://api.frostbyte.world';
async function api(path) {
const res = await fetch(`${BASE}${path}`, {
headers: { 'x-api-key': API_KEY }
});
return res.json();
}
async function auditSEO(url) {
const domain = new URL(url).hostname;
console.log(`\nš SEO Audit: ${url}\n${'='.repeat(50)}`);
// 1. Scrape the page for meta tags and structure
console.log('\nš Page Analysis...');
const page = await api(`/api/scraper/scrape?url=${encodeURIComponent(url)}`);
if (page.metadata) {
const m = page.metadata;
console.log(` Title: ${m.title || 'ā MISSING'}`);
console.log(` Title length: ${(m.title || '').length}/60 ${(m.title || '').length > 60 ? 'ā ļø Too long' : 'ā
'}`);
console.log(` Description: ${m.description ? m.description.slice(0, 80) + '...' : 'ā MISSING'}`);
console.log(` Desc length: ${(m.description || '').length}/160 ${(m.description || '').length > 160 ? 'ā ļø Too long' : 'ā
'}`);
console.log(` Canonical: ${m.canonical || 'ā ļø Not set'}`);
console.log(` OG Image: ${m.ogImage ? 'ā
' : 'ā MISSING'}`);
console.log(` Robots: ${m.robots || 'Not set (defaults to index)'}`);
}
// Check headings structure
if (page.content) {
const h1s = (page.content.match(/<h1[^>]*>/gi) || []).length;
const h2s = (page.content.match(/<h2[^>]*>/gi) || []).length;
console.log(`\n Headings:`);
console.log(` H1 tags: ${h1s} ${h1s === 1 ? 'ā
' : h1s === 0 ? 'ā Missing H1' : 'ā ļø Multiple H1s'}`);
console.log(` H2 tags: ${h2s} ${h2s > 0 ? 'ā
' : 'ā ļø No H2 tags'}`);
}
// Count links
if (page.links) {
const internal = page.links.filter(l => l.includes(domain)).length;
const external = page.links.length - internal;
console.log(`\n Links: ${page.links.length} total (${internal} internal, ${external} external)`);
}
// 2. Check DNS records
console.log('\nš DNS & Hosting...');
const dns = await api(`/api/dns/lookup?domain=${domain}`);
if (dns.records) {
const aRecords = dns.records.filter(r => r.type === 'A');
const cnameRecords = dns.records.filter(r => r.type === 'CNAME');
const mxRecords = dns.records.filter(r => r.type === 'MX');
console.log(` A Records: ${aRecords.map(r => r.value).join(', ') || 'None'}`);
console.log(` CNAME: ${cnameRecords.map(r => r.value).join(', ') || 'None'}`);
console.log(` MX Records: ${mxRecords.length > 0 ? 'ā
Email configured' : 'ā ļø No MX records'}`);
}
// 3. Check hosting IP info
if (dns.records) {
const ip = dns.records.find(r => r.type === 'A')?.value;
if (ip) {
const geo = await api(`/api/ip/geo?ip=${ip}`);
if (geo.country) {
console.log(` Server: ${geo.city || '?'}, ${geo.country} (${geo.isp || geo.org || '?'})`);
console.log(` Hosting: ${geo.org || geo.isp || 'Unknown'}`);
}
}
}
// 4. Take a screenshot
console.log('\nšø Screenshot...');
const screenshot = await api(`/api/screenshot/take?url=${encodeURIComponent(url)}&width=1280&height=800`);
if (screenshot.url) {
console.log(` Saved: ${screenshot.url}`);
}
// Generate score
console.log(`\n${'='.repeat(50)}`);
console.log('š Quick Score:');
let score = 0;
let total = 0;
const checks = [
[!!page.metadata?.title, 'Has title tag'],
[(page.metadata?.title || '').length <= 60, 'Title under 60 chars'],
[!!page.metadata?.description, 'Has meta description'],
[(page.metadata?.description || '').length <= 160, 'Description under 160 chars'],
[!!page.metadata?.ogImage, 'Has OG image'],
[!!page.metadata?.canonical, 'Has canonical URL'],
[(page.content?.match(/<h1[^>]*>/gi) || []).length === 1, 'Exactly one H1'],
[(page.content?.match(/<h2[^>]*>/gi) || []).length > 0, 'Has H2 headings'],
[dns.records?.some(r => r.type === 'MX'), 'MX records configured'],
[!!screenshot?.url, 'Page renders successfully'],
];
checks.forEach(([pass, label]) => {
total++;
if (pass) score++;
console.log(` ${pass ? 'ā
' : 'ā'} ${label}`);
});
console.log(`\n Score: ${score}/${total} (${Math.round(score/total*100)}%)`);
}
// Run it
const url = process.argv[2] || 'https://example.com';
auditSEO(url).catch(console.error);
Step 3: Run Your Audit
export FROSTBYTE_KEY="your-api-key"
node seo-audit.js https://dev.to
Output:
š SEO Audit: https://dev.to
==================================================
š Page Analysis...
Title: DEV Community
Title length: 13/60 ā
Description: A constructive and inclusive social network for software develo...
Desc length: 95/160 ā
Canonical: https://dev.to/
OG Image: ā
Robots: Not set (defaults to index)
Headings:
H1 tags: 1 ā
H2 tags: 12 ā
Links: 87 total (64 internal, 23 external)
š DNS & Hosting...
A Records: 151.101.1.38, 151.101.65.38
CNAME: None
MX Records: ā
Email configured
Server: San Francisco, US (Fastly)
Hosting: Fastly, Inc
šø Screenshot...
Saved: https://api.frostbyte.world/screenshots/dev.to-1709...png
==================================================
š Quick Score:
ā
Has title tag
ā
Title under 60 chars
ā
Has meta description
ā
Description under 160 chars
ā
Has OG image
ā
Has canonical URL
ā
Exactly one H1
ā
Has H2 headings
ā
MX records configured
ā
Page renders successfully
Score: 10/10 (100%)
Extending It
This basic auditor covers the fundamentals. Here are ideas to make it more powerful:
Add response time checking
const start = Date.now();
const res = await fetch(url);
const loadTime = Date.now() - start;
console.log(` Load time: ${loadTime}ms ${loadTime < 1000 ? 'ā
' : 'ā ļø Slow'}`);
Batch audit multiple pages
const pages = [
'https://example.com',
'https://example.com/about',
'https://example.com/blog',
];
for (const page of pages) {
await auditSEO(page);
}
Save results as JSON
const results = {
url,
timestamp: new Date().toISOString(),
score: `${score}/${total}`,
checks: checks.map(([pass, label]) => ({ pass, label })),
};
require('fs').writeFileSync('audit.json', JSON.stringify(results, null, 2));
How It Works Under the Hood
Each audit makes 4 API calls:
| API | What It Does | Credits |
|---|---|---|
/api/scraper/scrape |
Extracts HTML, meta tags, links, headings | 1 |
/api/dns/lookup |
Resolves DNS records (A, CNAME, MX, TXT) | 1 |
/api/ip/geo |
Looks up server location and hosting provider | 1 |
/api/screenshot/take |
Takes a full-page screenshot | 1 |
That's 4 credits per audit. With 200 free credits, you can audit 50 websites before needing to top up.
Why Build Your Own?
Commercial SEO tools charge $99-449/month. If you only need basic checks ā meta tags, headings, DNS, screenshots ā a DIY solution with free APIs gets you 90% of the value at zero cost. Plus you own the data and can customize the checks.
For automated monitoring, wrap this in a cron job or GitHub Action to audit your site daily and catch regressions before they hurt your rankings.
Get your free API key at agent-gateway-kappa.vercel.app ā 200 credits, no signup, no credit card. The API supports 40+ endpoints including IP geolocation, crypto prices, DNS, screenshots, web scraping, and more.
Top comments (0)