Ordinary DNS answers one question: where does this name point right now? Fraud infrastructure rarely stays put. Domains rotate, IPs get swapped, registrars change. What does not change is the history of relationships that already happened.
Passive DNS (pDNS) stores those observed answers over time: which name resolved to which address, and for how long. On IP99 (by ThreatHunter) you get a free trial of that history — forward lookup, reverse lookup, and a light malware-list check — without inventing a key for the first queries.
This post walks a practical pivot: domain → historical IPs → other names on those IPs, then how to read the timestamps without fooling yourself.
Why pivoting beats a single lookup
A phishing page that was taken down yesterday still leaves footprints. The server it used three months ago often still hosts other names from the same crew. IP99's own product copy puts it bluntly: a retired phish domain's old resolver address frequently carries dozens more domains from the same operators.
That is infrastructure pivoting. Certificate Transparency (CT) can show you a lookalike was signed early. pDNS shows you where it landed and who else shared the machine. Either alone is half a picture; together they close a loop. We covered CT in an earlier post — here the focus is the DNS side.
Forward lookup: history for one name
Free trial, no key, ten queries per client IP per day (signing in does not raise this particular quota):
curl https://ip99.com/v1/domain/dns/example.com
A live call on 20 Oct 2026 (same shape as the 15 Oct check) returned corpus_last_seen: "2026-08-15", twenty-one records (A, AAAA, MX, NS, TXT), and a profile that enriched landing IPs with country, city, ASN, and ASN org. Shape (trimmed):
{
"domain": "example.com",
"corpus_last_seen": "2026-08-15",
"count": 21,
"has_more": false,
"data": [
{
"rrtype": "A",
"value": "104.20.23.154",
"first_seen": "2026-08-15",
"last_seen": "2026-08-15"
}
],
"profile": {
"malware": { "listed": false, "last_listed": null, "days_listed": 0 },
"record_types": { "A": 12, "AAAA": 2, "MX": 1, "NS": 4, "TXT": 2 }
}
}
Six record types are covered, not only A. Mail and nameserver churn show up in the same response. Empty data: [] is a real answer (no observed rows), not an error. Bad parameters return HTTP 400 with a clear code — automate on status, not on emptiness.
Do not misread last_seen
This is the field people get wrong most often. IP99 imports resolution corpora in periodic batches. Seeing a last_seen a month ago usually means that snapshot's cutoff, not that the name went dark. Read last_seen against the top-level corpus_last_seen. Do not hard-code a date in your pipeline.
A separate path — newly registered domains for a calendar day — updates daily and has different time semantics. Do not apply the same interpretation to both.
Times are UTC. Aggregate by day only after you account for that.
Reverse lookup: who else shared the address
Once you have a landing IP, flip the question:
curl https://ip99.com/v1/domain/reverse/104.20.23.154
The same day's call returned 432 co-hosted names on that Cloudflare anycast address (has_more: false for that page). That volume is useful and noisy at once. Shared CDN and hosting IPs can reverse to hundreds or thousands of unrelated tenants. Treat reverse hits as candidates, then filter with CT fingerprints, malware-list days, registrar timing, or content similarity — not as automatic guilt-by-IP.
Limits to know: up to 1000 rows per response, paginate with ?offset=N and stop when has_more is false. Full-table reverse can take seconds (the service waits up to about 25s). Pass a bare address only — no :443, no /32. Private addresses are valid query values when they appear in real DNS history; they are not rejected as "invalid IP".
IP99 does not do active resolution, subdomain brute force, or one-shot bulk export of an IP's entire map through this trial surface. What you get is observed history, page by page.
Enrich the egress, not just the name
After you shortlist an IP that looks like dedicated fraud hosting (or before you block a signup from that space), check the address itself with the free no-key risk API:
curl https://ip99.com/v1/ip/8.8.8.8
Example shape from the same day:
{
"ip": "8.8.8.8",
"evidence_state": "none",
"risk": { "score": 0, "level": "none", "signals": ["hosting"] },
"network": { "asn": 15169, "usage_type": "IDC" },
"geo": { "country": "US", "city": "Mountain View" }
}
score is freshness and strength of verifiable risk evidence, not the probability that someone is an attacker. evidence_state: none with score 0 means no current evidence in the feed — a coverage gap, not a clean bill of health. hosting here is a network property (datacenter / CDN-class space), not a proxy verdict; do not block solely on hosting or mobile.
Anonymous quota for /v1/ip is separate from the pDNS trial (500/day per client IP; signed-in accounts get more). Same honesty rule across products: absence of a hit is not proof of safety.
A short workflow you can script
- Start from a reported phish or lookalike domain.
-
GET /v1/domain/dns/{domain}— collect historical A/AAAA (and note NS/MX if they jumped). - For each non-CDN-looking landing IP,
GET /v1/domain/reverse/{ip}— build a candidate cluster. - Cross-check interesting names with CT (
/v1/domain/certs/...) when you need issuance timing. -
GET /v1/ip/{ip}on egress you might gate — branch onevidence_state, then score, then signals. - Prefer step-up friction over hard bans when the address sits on shared residential or mobile space.
Optional helpers on the same free trial quota: GET /v1/domain/malware/{domain} for a millisecond list check, and GET /v1/domain/registered/{YYYY-MM-DD} for the first 1000 newly registered names that day (no pagination on that endpoint).
Coverage honesty
pDNS is observation, not authority. Sensor placement matters. Niche, brand-new, or region-local names may simply never appear. IP99 says that gap out loud: treat "we have no rows" differently from "we have rows and no malware hit." No single feed — this one included — sees the whole proxy or phishing market.
Try it
Open the interactive page at ip99.com/domain, or start with the curls above. For risk and city-level geo (China down to district) on any public IP, use ip99.com or curl https://ip99.com/v1/ip/{ip}. Docs and OpenAPI live under /api and /openapi.json if you want to generate a client instead of pasting shell.
Every answer on the risk side is a judgement that expires. On the DNS side, every row is a relationship that was observed — with a corpus clock you must read next to last_seen. Use both clocks, and you spend less time chasing ghosts.
Top comments (0)