A page tells you what its owner wants you to see. The domain underneath tells you things that are harder to fake: when it was registered, whether it can be transferred, whether it can receive mail, and whether the certificate a browser is about to trust is valid and actually issued for that name.
That information serves two very different jobs. Pointed outward, it is a trust signal: a payout request from a supplier domain registered nine days ago deserves a second look, and so does a lookalike of your brand. Pointed inward, it is monitoring: domains lapse, certificates expire, and DNS changes in ways nobody approved. Both use the same four APIs, and both reward knowing exactly which fields you can rely on.
01The registration record: WHOIS and domain age
WHOIS Lookup (whoislookup) reads the registry's RDAP or WHOIS record for a domain. It is a GET with a domain parameter and costs 8 credits.
curl "https://api.apiverve.com/v1/whoislookup?domain=example.com" \
-H "x-api-key: YOUR_API_KEY"createdDate, expiryDate, registrar and statusDetails are the fields that drive decisions.
For example.com the record shows a createdDate of 1995-08-14T04:00:00Z, an expiryDate, the registrar, two Cloudflare nameservers, and three EPP status codes. The raw codes are in domainStatus as the registry publishes them; the API also gives you statusDetails, where each code is normalised (clientTransferProhibited), attributed to whoever set it (registrar or registry) and explained in plain English. On top of those it derives booleans you would otherwise compute yourself: isTransferLocked, isOnHold and isPendingDelete.
Those three flags are the ones worth alerting on for your own domains:
| Flag | What it means for you |
|---|---|
isTransferLocked: false | Anyone who gets into your registrar account, or social-engineers it, can move the domain. Lock it. |
isOnHold: true | The domain is not resolving. Usually a billing or verification problem at the registrar. |
isPendingDelete: true | The registration lapsed and is in redemption or deletion. You have days, not weeks. |
Registrant details are another matter. Since GDPR, most registries redact personal contact data, and many registrants use privacy services on top. registrantOrganization and registrantCountry appear only when published, and isPrivacyProtected tells you when the contact was redacted or proxied. Privacy protection is not a negative signal. It is the default at many registrars and is what a careful individual should do. Never score a domain down for it.
The record also carries domainAgeDays and domainAgeYears. Paid plans add isRecentlyRegistered, trustScore and trustLevel; on other plans you compute what you need from domainAgeDays, which is available everywhere.
When age is all you want, Domain Age (domainage) answers directly. GET with domain, 10 credits.
curl "https://api.apiverve.com/v1/domainage?domain=google.com" \
-H "x-api-key: YOUR_API_KEY"ageCategory is the bracket; isNewlyRegistered flips true under 30 days.
It returns createdDate, the age in days, months and years, a readable domainAgeText such as "29 years, 1 month", an ageCategory bracket (new under 30 days, young under a year, established under 5 years, mature under 10, then veteran) and isNewlyRegistered. The brackets are convenient for rules, and they spare you the date arithmetic.
One behaviour to handle explicitly: for a domain with no published registration date, typically one that is not registered at all, Domain Age responds with a 404 and the error "No registration date is published for this domain." That is an answer, not an outage. Catch it and record unregistered, rather than retrying.
WHOIS Lookup reports the same situation differently. For an unregistered name it still returns status: "ok", but the record is nearly empty: no createdDate, an empty nameServers list and a registration status of null. So the test for "is this registered at all" is the presence of createdDate, not the HTTP status. Two APIs, two conventions; code for each one as it actually behaves.
A vendor's domain shows isPrivacyProtected: true and no registrantOrganization. How should your trust check treat this?
02The plumbing: DNS records
DNS Lookup (dnslookup) returns the published DNS records for a domain. GET with domain, 6 credits. Pass the bare registrable domain: no protocol, no path, and not a subdomain.
curl "https://api.apiverve.com/v1/dnslookup?domain=google.com" \
-H "x-api-key: YOUR_API_KEY"records.A, records.AAAA and records.MX are available on every plan; summary.hasMailServers is the quick check.
On every plan you get records.A (IPv4), records.AAAA (IPv6) and records.MX (mail servers, each with exchange and priority), plus a summary with hasIPv6 and hasMailServers. Paid plans add NS, TXT, CNAME and SOA records and summary.hasSPF. Build your logic on the free fields and treat the rest as enrichment when present, the same pattern as every lesson in this course.
For trust decisions, MX is the interesting record. A company domain that sends you invoices but publishes no mail servers is odd. A brand-new domain with mail servers and nothing else (no website, no history) is the classic shape of a domain registered to send one campaign.
For monitoring, DNS drift is the risk. Store the sorted list of A records and MX exchanges for your important domains and alert when they change. Expect A records on large sites to rotate constantly (Google returns a different set of addresses from run to run), so for domains behind a CDN or a big provider, compare MX and, on paid plans, NS, and leave A records out of the alert.
03The certificate: SSL checks
SSL Certificate Checker (sslchecker) connects to the site and inspects the certificate it actually serves. GET with domain, 10 credits.
curl "https://api.apiverve.com/v1/sslchecker?domain=expired.badssl.com" \
-H "x-api-key: YOUR_API_KEY"A deliberately broken test site: isExpired is true, isTrusted is false and trustError is CERT_HAS_EXPIRED.
expired.badssl.com exists to serve an expired certificate, which makes it a good test fixture. Notice that several fields answer different questions, and a certificate can fail any one of them:
| Field | Question it answers |
|---|---|
isValid / isExpired | Is the current time inside valid_from to valid_to? |
isTrusted / trustError | Does the chain verify to a trusted root? If not, why (CERT_HAS_EXPIRED, and so on)? |
hostnameMatches | Does the certificate cover this name at all? |
protocol / cipher | What TLS version and cipher the handshake negotiated |
hsts | Whether the site sends Strict-Transport-Security, and its maxAge |
A certificate can be within its dates and still untrusted (self-signed, missing intermediate) or issued for a different hostname. Checking only expiry misses two of the three ways a browser will show your users a warning.
For monitoring, the field you want is days until expiry. daysUntilExpiry and isExpiringSoon are premium. valid_to is available on every plan and comes in the GMT format OpenSSL prints, like Apr 22 23:59:59 2027 GMT. Both JavaScript's Date and Python's strptime with %b %d %H:%M:%S %Y GMT can parse it, so computing the number yourself is one line. Note the double space OpenSSL uses before single-digit days (Apr 9). strptime tolerates it, but a regex or a fixed-width slice will not, so normalise whitespace before any hand-rolled parsing. Also, %b matches month names in the process locale; a server running a non-English locale fails on Apr.
04Build a domain trust check
Combining the signals is where judgment comes in. Here is an ES module for Node 18+ that returns a verdict with reasons, so a human reviewer can see why.
const KEY = process.env.APIVERVE_API_KEY;
async function get(api, params) {
const res = await fetch(`https://api.apiverve.com/v1/${api}?${new URLSearchParams(params)}`, {
headers: { 'x-api-key': KEY },
signal: AbortSignal.timeout(15000),
});
const body = await res.json().catch(() => null);
if (res.status === 404) return null; // no record for this input
if (!res.ok || body?.status !== 'ok') throw new Error(`${api} ${res.status}: ${body?.error}`);
return body.data;
}
export async function domainTrust(domain) {
const [whois, dns, ssl] = await Promise.allSettled([
get('whoislookup', { domain }),
get('dnslookup', { domain }),
get('sslchecker', { domain }),
]).then((r) => r.map((x) => (x.status === 'fulfilled' ? x.value : undefined)));
const reasons = [];
// an unregistered domain still returns status "ok", with no createdDate and status: null
if (whois && !whois.createdDate) return { domain, verdict: 'block', reasons: ['no registration record'] };
if (whois) {
if (whois.domainAgeDays < 30) reasons.push(`registered ${whois.domainAgeDays} days ago`);
if (whois.isOnHold || whois.isPendingDelete) reasons.push('registration on hold or pending delete');
} else reasons.push('whois unavailable');
if (dns && !dns.summary.hasMailServers) reasons.push('publishes no mail servers');
if (ssl && (!ssl.isTrusted || !ssl.hostnameMatches)) reasons.push(`certificate problem: ${ssl.trustError ?? 'hostname mismatch'}`);
const hard = reasons.some((r) => r.startsWith('registered') || r.startsWith('registration'));
return { domain, verdict: hard ? 'review' : reasons.length ? 'caution' : 'ok', reasons };
}
console.log(await domainTrust(process.argv[2] ?? 'example.com'));Look at what it does not do. It never auto-blocks on age alone: new domains belong to new businesses too, and a startup that launched last week is a customer, not a fraudster. Youth sends the case to review. A missing registration record is the only hard block. A failed lookup is reported as unavailable and does not count against the domain, because your outage is not their fault. That is the shape every trust check should have: few hard rules, visible reasons, and a human in the loop for the grey zone.
Your monitor uses isValid alone to alert on certificate problems. Which failure will it miss?
05Monitor your own domains
Inward-facing, the same APIs become a nightly job: one run per domain you own, alerting when something needs a person. Here is the Python version, scheduled with cron or any job runner.
import os
from datetime import datetime, timezone
import requests
KEY = os.environ["APIVERVE_API_KEY"]
DOMAINS = ["example.com"] # your domains
WARN_DAYS = 21
def get(api, **params):
r = requests.get(f"https://api.apiverve.com/v1/{api}", params=params,
headers={"x-api-key": KEY}, timeout=15)
r.raise_for_status()
return r.json()["data"]
def days_until(value, fmt):
when = datetime.strptime(value, fmt).replace(tzinfo=timezone.utc)
return (when - datetime.now(timezone.utc)).days
def check(domain):
alerts = []
whois = get("whoislookup", domain=domain)
reg_days = days_until(whois["expiryDate"][:19], "%Y-%m-%dT%H:%M:%S")
if reg_days < 45:
alerts.append(f"registration expires in {reg_days} days")
if not whois.get("isTransferLocked"):
alerts.append("transfer lock is off")
ssl = get("sslchecker", domain=domain)
cert_days = days_until(" ".join(ssl["valid_to"].split()), "%b %d %H:%M:%S %Y GMT")
if cert_days < WARN_DAYS:
alerts.append(f"certificate expires in {cert_days} days")
if not ssl["isTrusted"] or not ssl["hostnameMatches"]:
alerts.append(f"certificate not trusted: {ssl.get('trustError')}")
return alerts
for d in DOMAINS:
for alert in check(d):
print(f"{d}: {alert}")The thresholds are different on purpose. Registration renewals can involve payment methods and approvals, so 45 days of warning is not too much. Certificates from automated issuers renew about a month before expiry; an alert at 21 days means renewal has already failed once, which is exactly when you want a person to look.
Run this once a day, not once an hour. None of these values move that fast, and four domains times two calls a day is a rounding error in your credit budget. Running it every minute buys nothing but cost.
06Where to go from here
You can now judge a domain from the outside and keep your own from lapsing quietly. The last lesson of this course turns from infrastructure back to content: scoring how readable a page is, how long it takes to read and what it is actually about. Continue with readability and content scoring.