Before you sign a contract with a vendor, onboard a new partner, or click a suspicious link, you probably want to know: who is behind this domain?
Tools like DomainTools and SecurityTrails charge $99+/month for this. But you can build your own domain intelligence tool in under 120 lines of JavaScript using free APIs — and it runs from the command line in seconds.
In this tutorial, we'll build a CLI that takes any domain and returns:
- DNS records (A, MX, NS, TXT, CNAME)
- WHOIS data (registrar, creation date, expiration)
- Server geolocation (country, city, ISP, hosting provider)
- A visual screenshot of the live site
- A risk assessment based on domain age, hosting, and DNS configuration
The result is a clean report you can use for vendor vetting, phishing analysis, or sales prospecting.
What We're Building
$ node domain-intel.js stripe.com
╔══════════════════════════════════════════════╗
║ DOMAIN INTELLIGENCE REPORT: stripe.com ║
╚══════════════════════════════════════════════╝
DNS Records
A: 185.166.143.0, 185.166.142.0
MX: aspmx.l.google.com (pri 1)
NS: ns-cloud-d1.googledomains.com
TXT: v=spf1 include:_spf.google.com ...
Server Location
IP: 185.166.143.0
Location: San Francisco, US
ISP: Fastly, Inc.
Hosting: Fastly CDN
WHOIS
Registrar: MarkMonitor Inc.
Created: 1995-09-12
Expires: 2031-09-11
Age: 30.5 years
Risk Score: 2/100 (Very Low)
✅ Domain age > 5 years
✅ Professional registrar
✅ SPF record configured
✅ MX records present
✅ CDN-hosted
Screenshot saved: stripe.com-screenshot.png
Prerequisites
- Node.js 18+ (for native
fetch) - A free API key from Frostbyte API Gateway — 200 free credits, no credit card
Step 1: Get Your Free API Key
curl -X POST https://api.frostbyte.systems/api/keys/create
You'll get back something like:
{
"key": "gw_abc123...",
"credits": 200,
"rateLimit": "60/min"
}
Save that key. Each domain lookup uses 3 credits (one per API call), so you get ~66 full reports for free.
Step 2: Build the Domain Intel Tool
Create a file called domain-intel.js:
import { writeFileSync } from 'fs';
const API_KEY = process.env.FROSTBYTE_KEY || 'your-key-here';
const BASE = 'https://api.frostbyte.systems/v1';
async function api(path, options = {}) {
const res = await fetch(`${BASE}${path}`, {
...options,
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
// ── Step A: DNS + WHOIS ──────────────────────────────
async function getDomainInfo(domain) {
const data = await api(`/agent-dns/resolve/${domain}`);
return data;
}
async function getWhois(domain) {
const data = await api(`/agent-dns/whois/${domain}`);
return data;
}
// ── Step B: Geolocate the server IP ──────────────────
async function geolocateIP(ip) {
const data = await api(`/agent-geo/geo/${ip}`);
return data;
}
// ── Step C: Screenshot the live site ─────────────────
async function screenshotDomain(domain) {
const data = await api('/agent-screenshot/screenshot', {
method: 'POST',
body: JSON.stringify({
url: `https://${domain}`,
viewport: 'desktop',
fullPage: false,
format: 'png',
}),
});
return data;
}
// ── Risk scoring logic ───────────────────────────────
function calculateRisk(dns, whois, geo) {
let score = 50; // Start neutral
const flags = [];
// Domain age
if (whois?.createdDate) {
const ageYears =
(Date.now() - new Date(whois.createdDate).getTime()) /
(365.25 * 24 * 60 * 60 * 1000);
if (ageYears > 5) {
score -= 20;
flags.push('✅ Domain age > 5 years');
} else if (ageYears < 0.5) {
score += 25;
flags.push('⚠️ Domain registered < 6 months ago');
} else if (ageYears < 1) {
score += 10;
flags.push('⚠️ Domain registered < 1 year ago');
}
} else {
score += 10;
flags.push('⚠️ WHOIS data unavailable');
}
// Registrar reputation
const trustedRegistrars = [
'markmonitor', 'cloudflare', 'godaddy', 'namecheap',
'google', 'gandi', 'hover', 'name.com',
];
if (whois?.registrar) {
const reg = whois.registrar.toLowerCase();
if (trustedRegistrars.some((t) => reg.includes(t))) {
score -= 10;
flags.push('✅ Professional registrar');
}
}
// SPF record
const hasSPF = dns?.TXT?.some((r) =>
(r.value || r).toString().includes('v=spf1')
);
if (hasSPF) {
score -= 5;
flags.push('✅ SPF record configured');
} else {
score += 5;
flags.push('⚠️ No SPF record');
}
// MX records
if (dns?.MX?.length > 0) {
score -= 5;
flags.push('✅ MX records present');
}
// DMARC
// (Would need _dmarc.domain TXT lookup — bonus feature)
// Hosting
if (geo?.isp) {
const cdn = ['cloudflare', 'fastly', 'akamai', 'amazon', 'google'];
if (cdn.some((c) => geo.isp.toLowerCase().includes(c))) {
score -= 10;
flags.push('✅ CDN-hosted');
}
}
return {
score: Math.max(0, Math.min(100, score)),
flags,
};
}
// ── Main: Assemble the report ────────────────────────
async function investigate(domain) {
console.log(`\n${'╔' + '═'.repeat(46) + '╗'}`);
console.log(`║ DOMAIN INTELLIGENCE REPORT: ${domain.padEnd(16)} ║`);
console.log(`${'╚' + '═'.repeat(46) + '╝'}\n`);
// Run DNS and WHOIS in parallel
console.log('Fetching DNS records...');
const [dns, whois] = await Promise.all([
getDomainInfo(domain).catch((e) => {
console.error(' DNS lookup failed:', e.message);
return null;
}),
getWhois(domain).catch((e) => {
console.error(' WHOIS lookup failed:', e.message);
return null;
}),
]);
// Print DNS
if (dns) {
console.log('\nDNS Records');
if (dns.A) console.log(` A: ${dns.A.join(', ')}`);
if (dns.AAAA) console.log(` AAAA: ${dns.AAAA.join(', ')}`);
if (dns.MX)
console.log(
` MX: ${dns.MX.map((r) => `${r.exchange || r} (pri ${r.priority || '?'})`).join(', ')}`
);
if (dns.NS) console.log(` NS: ${dns.NS.join(', ')}`);
if (dns.CNAME) console.log(` CNAME: ${dns.CNAME.join(', ')}`);
if (dns.TXT)
console.log(
` TXT: ${dns.TXT.map((t) => (t.length > 60 ? t.slice(0, 60) + '...' : t)).join('\n ')}`
);
}
// Print WHOIS
if (whois) {
console.log('\nWHOIS');
if (whois.registrar) console.log(` Registrar: ${whois.registrar}`);
if (whois.createdDate) console.log(` Created: ${whois.createdDate}`);
if (whois.expiresDate) console.log(` Expires: ${whois.expiresDate}`);
if (whois.createdDate) {
const years = (
(Date.now() - new Date(whois.createdDate).getTime()) /
(365.25 * 24 * 60 * 60 * 1000)
).toFixed(1);
console.log(` Age: ${years} years`);
}
}
// Geolocate the primary A record
const primaryIP = dns?.A?.[0];
let geo = null;
if (primaryIP) {
console.log('\nGeolocating server...');
geo = await geolocateIP(primaryIP).catch((e) => {
console.error(' Geolocation failed:', e.message);
return null;
});
if (geo) {
console.log('\nServer Location');
console.log(` IP: ${primaryIP}`);
if (geo.city) console.log(` Location: ${geo.city}, ${geo.country}`);
if (geo.isp) console.log(` ISP: ${geo.isp}`);
if (geo.org) console.log(` Org: ${geo.org}`);
}
}
// Risk score
const { score, flags } = calculateRisk(dns, whois, geo);
const level =
score < 20
? 'Very Low'
: score < 40
? 'Low'
: score < 60
? 'Medium'
: score < 80
? 'High'
: 'Critical';
console.log(`\nRisk Score: ${score}/100 (${level})`);
flags.forEach((f) => console.log(` ${f}`));
// Screenshot
console.log('\nCapturing screenshot...');
try {
const shot = await screenshotDomain(domain);
if (shot?.image) {
// image is base64 — decode and save
const buffer = Buffer.from(shot.image, 'base64');
const filename = `${domain.replace(/\./g, '-')}-screenshot.png`;
writeFileSync(filename, buffer);
console.log(`Screenshot saved: ${filename}`);
} else if (shot?.url) {
console.log(`Screenshot URL: ${shot.url}`);
}
} catch (e) {
console.log(` Screenshot failed: ${e.message}`);
}
console.log('\n' + '─'.repeat(48));
console.log('Powered by Frostbyte API — https://api-catalog-three.vercel.app');
return { dns, whois, geo, risk: { score, level, flags } };
}
// ── CLI entry ────────────────────────────────────────
const domain = process.argv[2];
if (!domain) {
console.error('Usage: node domain-intel.js <domain>');
console.error('Example: node domain-intel.js stripe.com');
process.exit(1);
}
investigate(domain.replace(/^https?:\/\//, '').replace(/\/.*$/, ''));
Step 3: Run It
export FROSTBYTE_KEY="gw_your_key_here"
node domain-intel.js github.com
How It Works
The tool chains three API calls together:
1. DNS + WHOIS (/v1/agent-dns)
The DNS API returns all record types for a domain in a single call. We also pull WHOIS data separately to get registrar info, creation date, and expiration — critical signals for assessing domain legitimacy.
// Get all DNS records
const dns = await api(`/agent-dns/resolve/example.com`);
// Returns: { A: [...], MX: [...], NS: [...], TXT: [...] }
// Get WHOIS / RDAP data
const whois = await api(`/agent-dns/whois/example.com`);
// Returns: { registrar, createdDate, expiresDate, nameServers, ... }
2. IP Geolocation (/v1/agent-geo)
Once we have the A record IPs, we geolocate them to find out where the server is physically hosted. This reveals the hosting provider, country, and ISP — useful for detecting if a "US company" is actually hosted in an unexpected jurisdiction.
const geo = await api(`/agent-geo/geo/185.166.143.0`);
// Returns: { country: "US", city: "San Francisco", isp: "Fastly", ... }
3. Visual Screenshot (/v1/agent-screenshot)
A screenshot of the live site gives you instant visual confirmation. Does it look like a real business? Is it a parking page? A phishing clone? One glance tells you what DNS records can't.
const shot = await api('/agent-screenshot/screenshot', {
method: 'POST',
body: JSON.stringify({ url: 'https://example.com', viewport: 'desktop' }),
});
// Returns: { image: "base64...", width: 1280, height: 800 }
The Risk Scoring Algorithm
The most useful part of this tool is the automated risk assessment. Here's how the scoring works:
| Signal | Effect on Score |
|---|---|
| Domain age > 5 years | -20 (safer) |
| Domain age < 6 months | +25 (riskier) |
| Trusted registrar (MarkMonitor, Cloudflare, etc.) | -10 |
| SPF record present | -5 |
| MX records configured | -5 |
| Hosted on CDN (Cloudflare, Fastly, etc.) | -10 |
| WHOIS data unavailable | +10 |
| No SPF record | +5 |
The score starts at 50 and adjusts based on these signals. A domain like stripe.com (30+ years old, MarkMonitor, SPF, CDN) lands around 2/100. A freshly registered domain with no email records might score 80+.
Real-World Use Cases
Vendor Security Assessment
Before connecting a third-party service to your infrastructure, run this tool to verify they're legitimate:
node domain-intel.js new-saas-vendor.com
Red flags to watch for:
- Domain registered less than a year ago
- No SPF/DMARC records (poor email security hygiene)
- Hosted on a residential ISP instead of a cloud provider
- WHOIS privacy on a supposedly enterprise company
Phishing Detection
Got a suspicious email with a link? Check the domain before clicking:
node domain-intel.js amaz0n-secure-login.com
Phishing domains almost always score high because they're newly registered, lack email infrastructure, and often use cheap registrars.
Sales Prospecting
Before reaching out to a company, understand their technical setup:
node domain-intel.js target-company.io
You can see what email provider they use (MX records), what CDN they're on, and how established they are. This is the same data sales intelligence platforms charge hundreds per month for.
Extending the Tool
Here are some ideas to make it more powerful:
Add DNS Propagation Check
The DNS API also supports propagation analysis across 8 resolvers:
const prop = await api(`/agent-dns/propagation/${domain}`);
// Shows if DNS records are consistent across Google, Cloudflare, Quad9, etc.
Add Web Scraping for Page Content
Use the scraper API to pull the actual page content and analyze it:
const page = await api(
`/agent-scraper/scrape?url=https://${domain}&format=markdown`
);
// Extract title, meta description, heading structure, outbound links
Batch Processing
Investigate multiple domains at once:
const domains = ['example1.com', 'example2.com', 'example3.com'];
const results = await Promise.all(domains.map(investigate));
Export as JSON
Add a --json flag for piping into other tools:
if (process.argv.includes('--json')) {
console.log(JSON.stringify(result, null, 2));
process.exit(0);
}
API Costs
Each full domain report uses:
| API Call | Credits |
|---|---|
| DNS Lookup | 1 |
| WHOIS Lookup | 1 |
| IP Geolocation | 1 |
| Screenshot | 1 |
| Total per domain | 4 |
With 200 free credits, you get 50 full domain reports without paying anything. If you need more, credits are $1 per 500 at api-catalog-three.vercel.app/topup.
Wrapping Up
In about 120 lines of JavaScript, you've built a domain intelligence tool that combines DNS analysis, server geolocation, visual screenshots, and automated risk scoring. It's the kind of tool that security teams, sales teams, and developers all find useful — and it runs locally with no dependencies beyond Node.js.
The full source code uses three Frostbyte APIs through a single API key:
- DNS Lookup — records, WHOIS, propagation
- IP Geolocation — server location, ISP, hosting provider
- Screenshots — visual confirmation of live sites
Get your free API key at api-catalog-three.vercel.app and start investigating domains in seconds.
Have questions or want to see more API tutorials? Drop a comment below or check out the full API docs.
Top comments (0)