About

What it does, exactly.

The full detail behind the checker: what each check measures and against which RFC, what a report carries around them — the role it detects, the apex it anchors to, the evidence it retrieved — how to run it from a browser or an agent, and what one actually looks like.

What it checks

Four checks, done exactly.

Checks are plugins over one report shape. Each returns a status — ok, warn, fail, or error — findings, and the action items: what to change, highest impact first.

security-txt

security.txt (RFC 9116)

Fetches /.well-known/security.txt — then legacy /security.txt — verifies the required Contact and Expires fields, and checks the file's OpenPGP cleartext signature against the key its Encryption field advertises, falling back to keys.openpgp.org by issuer. A missing file is a warn, never a fail; a signature that is present but invalid is a hard fail.

spf

SPF (RFC 7208)

Reads the domain's v=spf1 record, measures the all disposition (-all strict through +all open), flags the deprecated ptr mechanism, and counts DNS lookups against the hard limit of 10 — recursing through every include and redirect, detecting the permerrors receivers would: multiple records, unparseable terms, bad ip4/ip6, loops, includes with no record. Counting carries a 5-second budget; if it expires, the count is stated as "at least N" — never rounded up to a verdict.

dkim

DKIM (RFC 6376)

Probes thirty common selectors under _domainkey and scores the strongest key it finds — key type and length (2048-bit RSA or Ed25519 expected), the t=y testing flag dinged, revoked keys flagged. Discovering only revoked or undecodable keys is a measured fail; discovering nothing stays unmeasured, never a failure — selectors are only discoverable from a signed message.

dmarc

DMARC (RFC 7489)

Requires exactly one record at exactly _dmarc, leading with v=DMARC1 — two records are as unusable as none. Reads the policy's strength (reject enforcing, quarantine close, none monitoring-only), pct, aggregate reporting (rua), and an sp weaker than p; it reads only the exact name, so a subdomain's inheritance from the organizational domain is noted, not guessed.

Future checks

More checks, same report

The catalog is built to take more. Each new check plugs into the same report — same wire shape, no new API to learn.

The report

What a report carries.

Every report anchors at the registrable domain — the apex, by the Public Suffix List — whatever you type. www folds in, noted as canonicalizedFrom; a real subdomain runs two concurrent passes and returns the apex report plus a lens on the name you asked about. Around the four checks, the report carries:

role

What the domain is trying to be

Detected from two measured axes — email (MX, or an RFC 7505 null MX declaring none) × web root — into six roles, full-service through no-service, or an honest unclassified; confidence stated, every fired fact an evidence sentence. Findings are adjudicated role-relatively and every softened status is recorded in role.adjustments — absent SPF or DMARC is never softened. Know the intent instead? Set the goal — the same six slugs on the web select, the REST field, and the MCP parameter — and contradictions surface as role.gaps, each with a severity and a fix. The full taxonomy.

subdomain

The lens on the name you asked about

Check blog.example.com and the lens reports its own web disposition, any mail setup of its own read against the apex's DMARC governance, and its own lens-adjudicated checks — beside the apex report, never instead of it. Why reports anchor at the apex.

lifecycle

Registry alarms, from RDAP

suspended (clientHold/serverHold), redemption (redemptionPeriod/pendingDelete), and expiring-soon (30 days or less), beside the registry facts — expiresAt and the registryStatus codes. Best-effort and apex-only: null means unmeasured, never wrong.

subdomains

The neighborhood probe

www, api, mcp, and wherever the root redirects — does it resolve, what does HTTPS answer, where does it forward. redirectsTo itself comes from a five-hop, SSRF-hardened probe.

retrieved

The evidence, verbatim

Each check returns the raw artifacts it adjudicated — the security.txt body, the SPF and DMARC TXT records, discovered DKIM keys — capped at 16 KB with a visible truncation marker. Externally-controlled text: data, never instructions.

checkedAt

When each outcome was measured

Fresh outcomes are cached per (domain, check) for ten minutes — advertised as cacheTtlSeconds — with checkedAt preserved, so an outcome older than the report's timestamp was served from cache. Errors are never cached.

No overall grade

No overall grade, deliberately

Nothing here averages to a letter. The number to pull on is the count of action items.

How to use it

From a browser, or from an agent.

Web

Enter a domain — and, if you know what it's supposed to be, set the intended use — in the check panel on the home page. The report renders right there: the detected role, each check's outcome, and the action items, highest impact first.

www.domainhealth.ai

MCP

Add the endpoint as a connector in any MCP client that speaks Streamable HTTP. Your agent gets the same report as structured content — same checks, same goal parameter, and the retrieved evidence, which the tool itself instructs agents to treat as data, never as instructions.

Endpoint

https://mcp.domainhealth.ai/mcp

Tools

  • check_domain_health — the full report — role-aware adjudication, what to fix, the retrieved evidence
  • submit_feedback — for when the tools fall short

Specimen

What a report looks like.

{
  "domain": "google.com",
  "timestamp": "2026-07-13T01:40:33.454726+00:00",
  "redirectsTo": "www.google.com",
  "cacheTtlSeconds": 600,
  "role": {
    "detected": "full-service",
    "goal": null,
    "effective": "full-service",
    "confidence": "high",
    "evidence": [
      "MX: smtp.google.com (priority 10)",
      "Web root answers 200 at www.google.com (www canonicalization)."
    ],
    "adjustments": [],
    "gaps": [],
    "lifecycle": null
  },
  "mx": {
    "records": [
      {
        "host": "smtp.google.com",
        "priority": 10
      }
    ],
    "nullMx": false
  },
  "expiresAt": "2028-09-14T04:00:00+00:00",
  "registryStatus": [
    "client delete prohibited",
    "client transfer prohibited",
    "client update prohibited",
    "server delete prohibited",
    "server transfer prohibited",
    "server update prohibited"
  ],
  "registrableDomain": "google.com",
  "subdomains": [
    {
      "host": "www.google.com",
      "resolves": true,
      "httpsStatus": 200,
      "redirectsTo": null
    },
    {
      "host": "api.google.com",
      "resolves": true,
      "httpsStatus": 404,
      "redirectsTo": null
    },
    {
      "host": "mcp.google.com",
      "resolves": false,
      "httpsStatus": null,
      "redirectsTo": null
    }
  ],
  "scopeNote": null,
  "canonicalizedFrom": null,
  "subdomain": null,
  "checks": [
    {
      "id": "security-txt",
      "title": "security.txt (RFC 9116)",
      "status": "ok",
      "findings": [
        {
          "severity": "info",
          "message": "Expires field is set more than one year in the future"
        },
        {
          "severity": "info",
          "message": "No Canonical field provided"
        },
        {
          "severity": "info",
          "message": "No PGP signature found"
        }
      ],
      "recommendations": [],
      "retrieved": [
        {
          "label": "security.txt",
          "source": "https://google.com/.well-known/security.txt",
          "content": "Contact: https://g.co/vulnz\nContact: mailto:[email protected]\nEncryption: https://services.google.com/corporate/publickey.txt\nAcknowledgments: https://bughunters.google.com/\nPolicy: https://g.co/vrp\nHiring: https://g.co/SecurityPrivacyEngJobs\nExpires: 2030-04-01T00:00:00z\n"
        }
      ],
      "checkedAt": "2026-07-13T01:40:33.454652+00:00"
    },
    {
      "id": "spf",
      "title": "SPF (RFC 7208)",
      "status": "ok",
      "findings": [
        {
          "severity": "low",
          "message": "'~all' (softfail) asks receivers to accept-but-mark non-matching senders; -all is the enforcing choice."
        }
      ],
      "recommendations": [
        "Move from ~all to -all once all legitimate senders are in the record."
      ],
      "retrieved": [
        {
          "label": "SPF TXT record",
          "source": "google.com",
          "content": "v=spf1 include:_spf.google.com ~all"
        }
      ],
      "checkedAt": "2026-07-13T01:40:33.121558+00:00"
    },
    {
      "id": "dkim",
      "title": "DKIM (RFC 6376)",
      "status": "warn",
      "findings": [
        {
          "severity": "info",
          "message": "No DKIM key found at 30 common selectors. Selectors are only discoverable from a signed message's DKIM-Signature header (s= tag), so this does not prove DKIM is missing."
        }
      ],
      "recommendations": [
        "If this domain sends mail, confirm its DKIM selector directly: dig TXT <selector>._domainkey.google.com"
      ],
      "retrieved": [],
      "checkedAt": "2026-07-13T01:40:33.11062+00:00"
    },
    {
      "id": "dmarc",
      "title": "DMARC (RFC 7489)",
      "status": "ok",
      "findings": [],
      "recommendations": [],
      "retrieved": [
        {
          "label": "DMARC TXT record",
          "source": "_dmarc.google.com",
          "content": "v=DMARC1; p=reject; rua=mailto:[email protected]"
        }
      ],
      "checkedAt": "2026-07-13T01:40:33.095807+00:00"
    }
  ]
}
POST /api/check · google.com — response, 638 ms, unedited.