DEV Community

Ozor
Ozor

Posted on

How to Build a Domain Intelligence Tool in JavaScript (DNS + Geolocation + Screenshots)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You'll get back something like:

{
  "key": "gw_abc123...",
  "credits": 200,
  "rateLimit": "60/min"
}
Enter fullscreen mode Exit fullscreen mode

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(/\/.*$/, ''));
Enter fullscreen mode Exit fullscreen mode

Step 3: Run It

export FROSTBYTE_KEY="gw_your_key_here"
node domain-intel.js github.com
Enter fullscreen mode Exit fullscreen mode

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, ... }
Enter fullscreen mode Exit fullscreen mode

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", ... }
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Batch Processing

Investigate multiple domains at once:

const domains = ['example1.com', 'example2.com', 'example3.com'];
const results = await Promise.all(domains.map(investigate));
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)