OzorEver wanted to quickly check a website's SEO health without paying for expensive tools like Ahrefs or...
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:
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.
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);
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%)
This basic auditor covers the fundamentals. Here are ideas to make it more powerful:
const start = Date.now();
const res = await fetch(url);
const loadTime = Date.now() - start;
console.log(` Load time: ${loadTime}ms ${loadTime < 1000 ? '✅' : '⚠️ Slow'}`);
const pages = [
'https://example.com',
'https://example.com/about',
'https://example.com/blog',
];
for (const page of pages) {
await auditSEO(page);
}
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));
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.
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.