docs
stormdoor.dev
GATE

/v1/domains

Screens a bare domain for existence, registration age, parking, and brand impersonation.

METHOD
POST
BODY CAP
4 KiB
CHECKS
5
FINDING CODES
5

POST /v1/domains screens one domain name — not a full URL. Use it when you have a company or website domain from a form field (not a link someone clicked) and want to know whether it’s real, new, dormant, or impersonating something well known.

Request#

{ "domain": "paypa1.com" }
Field Type Required Notes
domain string yes 1–253 characters. Not format-validated at the boundary — a garbage string still flows through to each check’s own guard, which either finds nothing to check or reports it.

There are no optional fields yet.

Request body cap: 4 KB — far more than a single domain needs, with headroom for future optional fields.

Send the registrable (apex) domain, not a full hostname. RDAP registries index registration records at the apex level — POST /v1/domains {"domain":"www.cloudflare.com"} returns domain.not_registered, even though cloudflare.com itself is registered and resolves cleanly. This isn’t a bug in the check; RDAP genuinely has no record of a domain literally named www.cloudflare.com. Strip any subdomain before calling this gate.

Checks this gate runs#

domain.dns and domain.rdap make network calls (DNS-over-HTTPS and RDAP respectively); domain.parked and domain.typosquat are pure computation. All run on every call.

Check What it does
domain.dns DNS existence and nameserver lookup — see domain.not_found. Also feeds domain.parked.
domain.rdap Registry lookup for registration status and age — see domain.not_registered, domain.newly_registered.
domain.parked Nameserver match against known domain-parking services — see domain.parked.
domain.typosquat Typo similarity to well-known brand domains — see domain.typosquat.
domain.mx Mail-exchanger lookup for the domain — contributes data.mx only, no finding.

domain.mx is data-only: a domain without MX records isn’t itself a problem for this gate, so it emits nothing and just adds data.mx — an array of {host, preference} sorted by preference — when the domain has usable MX records. data.mx is absent (never null or empty) when the lookup fails, the domain has no MX records, or the domain publishes an RFC 7505 null MX. Same shape and same rule as /v1/emails’s data.mx.

RDAP coverage varies by TLD — some registries don’t run a public RDAP server. A TLD with no coverage degrades to “we don’t know” (no finding either way, but also no confirmation), never a false claim of registration status.

Example#

curl -s -X POST https://api.stormdoor.dev/v1/domains \
  -H "Authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{"domain":"paypa1.com"}'
{
  "ok": true,
  "verdict": "suspect",
  "findings": [
    {
      "code": "domain.typosquat",
      "severity": "medium",
      "message": "Domain \"paypa1.com\" looks like a typo of \"paypal.com\" (edit distance 1)"
    }
  ],
  "data": {
    "dns": {
      "resolves": true,
      "nameservers": ["ns1.markmonitor.com", "ns2.markmonitor.com"]
    },
    "mx": [{ "host": "bh.markmonitor.com", "preference": 1 }],
    "rdap": { "createdAt": "2000-10-20T23:17:07.000Z", "ageDays": 9473 }
  },
  "checks_run": ["domain.dns", "domain.mx", "domain.parked", "domain.rdap", "domain.typosquat"],
  "checks_skipped": [],
  "meta": { "gate": "domains", "ruleset": "reference-9f0e0204" }
}

Real response — paypa1.com (a digit substituted for a letter) resolves and is old, so RDAP and DNS both come back clean; only the typosquat check on the domain string itself fires. data.rdap/data.dns are always the gate’s own resolved values, even when nothing about them is a finding.

See response format for how checks_skipped reasons work, and don’t proxy that array to your own end users.

Finding codes#

Each code links to its own page with what it looks at, why it matters and what to do.

domain.newly_registeredmediumdomain.rdapsuspect
domain.not_foundhighdomain.dnsblock
domain.not_registeredhighdomain.rdapblock
domain.parkedmediumdomain.parkedsuspect
domain.typosquatmediumdomain.typosquatsuspect