API Documentation

Use the site's checks as a JSON API. No sign-up and no API key.

REST · JSONv1No sign-up or keyOpenAPI 3.1

Getting started

Free, no sign-up. The base URL is https://certadomain.com/api/v1. Use within the limits is welcome (Terms of Use). Every check uses public DNS, registry and CT-log data only; target web pages are never fetched.

curl

curl -s "https://certadomain.com/api/v1/overview?domain=example.com"

Python

import time
import requests

def check(domain):
    r = requests.get("https://certadomain.com/api/v1/overview", params={"domain": domain}, timeout=30)
    if r.status_code == 429:
        time.sleep(int(r.headers["Retry-After"]))
        return check(domain)
    r.raise_for_status()
    return r.json()

report = check("example.com")
print(report["grade"], report["score"])

JavaScript

const res = await fetch("https://certadomain.com/api/v1/overview?domain=example.com");
if (!res.ok) {
  const problem = await res.json();   // application/problem+json
  throw new Error(problem.error);     // e.g. "invalid_domain"
}
const report = await res.json();
console.log(report.grade, report.score);
Base URLhttps://certadomain.com/api/v1
AuthenticationNone (there are no API keys)
FormatJSON (UTF-8) in and out. Errors are application/problem+json
CompatibilityWithin /v1 fields are only added, never removed or renamed. Ignore unknown properties.
Spec/api/v1/openapi.json

Endpoints

Analysis

GET/overviewcost 1

Email authentication (60%), DNS health (25%) and registration (15%) in one 0-100 score with an A-E grade, plus up to 6 prioritized actions.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/overview?domain=example.com"

Response 200 (abridged)

{
  "domain": "example.com",
  "target": "example.com",
  "score": 96,
  "grade": "A",
  "email": {
    "score": 93,
    "grade": "protected",
    "findings": [
      {
        "category": "dmarc",
        "code": "DMARC_POLICY_REJECT",
        "severity": "ok"
      },
      {
        "category": "dmarc",
        "code": "DMARC_NO_RUA",
        "severity": "warning"
      },
      {
        "category": "spf",
        "code": "SPF_LOOKUP_OK",
        "severity": "ok",
        "params": {
          "count": 0,
          "limit": 10
        }
      }
    ],
    "spf_lookups": {
      "count": 0,
      "limit": 10,
      "voids": 0
    }
  },
  "dns": {
    "score": 100,
    "zone": "example.com",
    "dnssec": "valid",
    "findings": [
      {
        "category": "ns",
        "code": "NS_COUNT_OK",
        "severity": "ok",
        "params": {
          "count": 2
        }
      },
      {
        "category": "ns",
        "code": "NS_DIVERSITY_OK",
        "severity": "ok",
        "params": {
          "networks": 6
        }
      }
    ]
  },
  "registration": {
    "score": 100,
    "data": {
      "registered": "1995-08-14",
      "expires": "2027-08-13",
      "registrar": "RESERVED-Internet Assigned Numbers Authority",
      "locked": true
    }
  },
  "actions": [
    {
      "category": "dmarc",
      "code": "DMARC_NO_RUA",
      "severity": "warning"
    }
  ],
  "analyzed_at": 1794625564,
  "cached": true
}

Key fields

score / gradeOverall score and grade. Any critical finding caps the score at 69.
email · dns · registrationScore and findings per area. An area that could not be measured is left out and the rest re-weighted.
actions[]Highest-priority findings, critical first.
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.
GET/dnshealthcost 1

Nameserver redundancy and authoritative answers, SOA / delegation consistency, DNSSEC, CAA and MX resolution. Nameservers are queried directly.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/dnshealth?domain=example.com"

Response 200 (abridged)

{
  "domain": "example.com",
  "zone": "example.com",
  "score": 100,
  "grade": "A",
  "findings": [
    {
      "category": "ns",
      "code": "NS_COUNT_OK",
      "severity": "ok",
      "params": {
        "count": 2
      }
    },
    {
      "category": "ns",
      "code": "NS_DIVERSITY_OK",
      "severity": "ok",
      "params": {
        "networks": 6
      }
    },
    {
      "category": "dnssec",
      "code": "DNSSEC_VALID",
      "severity": "ok"
    }
  ],
  "nameservers": [
    {
      "host": "elliott.ns.cloudflare.com",
      "ipv4": [
        "108.162.195.228"
      ],
      "ipv6": [
        "2606:4700:58::a29f:2ce4"
      ],
      "status": "ok",
      "serial": 2420077714
    }
  ],
  "dnssec": "valid",
  "caa_issuers": [],
  "analyzed_at": 1794626559,
  "cached": false
}

Key fields

zoneThe zone that was found (registrable domain).
nameservers[]Addresses, status (ok, lame, timeout ...) and SOA serial of each nameserver.
dnssecvalid, off, broken, unverified ...
findings[]code (stable identifier), severity (critical / warning / info / ok) and, where needed, params.
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.
GETPOST/dmarccost 1

SPF, DKIM, DMARC and MX per the RFCs: subdomains inherit the organization domain's DMARC, and SPF is counted recursively against the 10-lookup limit. For POST, send {"domain":"example.com"} as the body.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/dmarc?domain=example.com"

curl -s -X POST https://certadomain.com/api/v1/dmarc -H "content-type: application/json" \
  -d '{"domain":"example.com"}'

Response 200 (abridged)

{
  "domain": "example.com",
  "summary": {
    "grade": "protected"
  },
  "records": {
    "dmarc": "v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s",
    "spf": "v=spf1 -all",
    "dkim": {},
    "mx": [
      {
        "pref": 0,
        "host": ""
      }
    ]
  },
  "spf_lookups": {
    "count": 0,
    "limit": 10,
    "voids": 0
  },
  "findings": [
    {
      "category": "dmarc",
      "code": "DMARC_POLICY_REJECT",
      "severity": "ok"
    },
    {
      "category": "dmarc",
      "code": "DMARC_NO_RUA",
      "severity": "warning"
    },
    {
      "category": "spf",
      "code": "SPF_LOOKUP_OK",
      "severity": "ok",
      "params": {
        "count": 0,
        "limit": 10
      }
    },
    {
      "category": "dkim",
      "code": "DKIM_NONE_COMMON",
      "severity": "info"
    },
    {
      "category": "mx",
      "code": "MX_FOUND",
      "severity": "ok",
      "params": {
        "count": 1
      }
    }
  ],
  "analyzed_at": 1794626562,
  "cached": false
}

Key fields

summary.gradeprotected / partial / monitoring / unprotected.
spf_lookupsSPF breaks when count exceeds limit (10).
records.dkimPresence of common selectors. A miss does not prove DKIM is unset.
findings[]code (stable identifier), severity (critical / warning / info / ok) and, where needed, params.
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.
GET/phishingcost 1

A heuristic from domain-name patterns, DNS signals and registration age. The site's content is not inspected.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/phishing?domain=example.com"

Response 200 (abridged)

{
  "domain": "example.com",
  "verdict": "low",
  "score": 0,
  "registration": {
    "source": "rdap",
    "registered": "1995-08-14",
    "changed": "2026-08-14",
    "expires": "2027-08-13",
    "registrar": "RESERVED-Internet Assigned Numbers Authority",
    "age_days": 11415,
    "days_to_expiry": 272,
    "locked": true,
    "dnssec": true
  },
  "findings": [
    {
      "code": "LOOKS_CLEAN",
      "severity": "ok"
    }
  ],
  "analyzed_at": 1794626559,
  "cached": false
}

Key fields

verdict / scorelow / medium / high and the score behind it.
registrationRegistration date, expiry, registrar, lock state. Empty for TLDs without RDAP.
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.

Discovery

GET/subdomainscost 1

Subdomains from Certificate Transparency logs (crt.sh and Cert Spotter). Only names that ever had a certificate appear, and entries may be stale. Results are stored for 2 hours.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/subdomains?domain=example.com"

Response 200 (abridged)

{
  "domain": "example.com",
  "subdomains": [
    "dev.example.com",
    "emudhra-lab.example.com",
    "example.com",
    "m.example.com",
    "products.example.com",
    "support.example.com",
    "www.example.com"
  ],
  "count": 7,
  "sources": [
    "crt.sh",
    "certspotter"
  ]
}

Key fields

subdomains[]Host names found (deduplicated, sorted).
sources[]Which sources answered. One succeeding is enough.
—This is the only tool without analyzed_at and cached.

If both sources fail the response is 502 ct_unavailable.

GET/takeovercost 5

Checks the CNAMEs of up to 100 CT-discovered subdomains for dangling targets. DNS only; no page is fetched. Can take 10+ seconds.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/takeover?domain=example.com"

Response 200 (abridged)

{
  "domain": "example.com",
  "total": 6,
  "scanned": 6,
  "truncated": false,
  "failed": 0,
  "sources": [
    "crt.sh",
    "certspotter"
  ],
  "summary": {
    "critical": 1,
    "warning": 0,
    "info": 0
  },
  "findings": [
    {
      "code": "TAKEOVER_DANGLING_SERVICE",
      "severity": "critical",
      "params": {
        "subdomain": "old.example.com",
        "target": "example-app.azurewebsites.net",
        "service": "Azure App Service"
      }
    }
  ],
  "analyzed_at": 1794626569,
  "cached": false
}

Key fields

summaryCounts of critical / warning / info.
findings[].codeTAKEOVER_DANGLING_SERVICE, TAKEOVER_UNREGISTERED_TARGET, DANGLING_CNAME, TAKEOVER_CANDIDATE.
total · scanned · truncatedFound, scanned, and whether the cap cut the list.
failedSubdomains that could not be checked (timeouts and so on).
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.

The finding in this example is illustrative, to show the shape of a hit; it is not a real result.

GET/lookalikecost 5

Generates up to 220 typosquatting / homograph candidates and returns the registered ones ranked by risk. Takes 4-6 seconds. Being registered is not proof of abuse.

Parameters

NameRequiredDescription
domainyesA domain or host name. A URL is reduced to its host; internationalized names become punycode.

Request

curl -s "https://certadomain.com/api/v1/lookalike?domain=paypal.com"

Response 200 (abridged)

{
  "domain": "paypal.com",
  "target": "paypal.com",
  "generated": 90,
  "scanned": 90,
  "truncated": false,
  "failed": 0,
  "registered": 65,
  "summary": {
    "high": 1,
    "medium": 21,
    "low": 43
  },
  "analyzed_at": 1794626569,
  "cached": false,
  "items": [
    {
      "domain": "paypal-service.com",
      "display": "paypal-service.com",
      "type": "KEYWORD",
      "mx": true,
      "web": true,
      "ns": [
        "ns69.domaincontrol.com",
        "ns70.domaincontrol.com"
      ],
      "same_ns": false,
      "registered": "2026-08-07",
      "registrar": "GoDaddy.com, LLC",
      "age_days": 99,
      "risk": "high",
      "signals": [
        "RECENT",
        "MX",
        "WEB"
      ]
    },
    {
      "domain": "p4yp4l.com",
      "display": "p4yp4l.com",
      "type": "HOMOGLYPH",
      "mx": true,
      "web": false,
      "ns": [
        "anahi.ns.cloudflare.com",
        "bruce.ns.cloudflare.com"
      ],
      "same_ns": false,
      "registered": "2026-02-05",
      "registrar": "Cloudflare, Inc.",
      "age_days": 282,
      "risk": "medium",
      "signals": [
        "MX"
      ]
    }
  ]
}

Key fields

summaryCounts of high / medium / low.
items[].typeHOMOGLYPH, IDN_HOMOGRAPH, TLD_SWAP, KEYWORD, OMISSION ...
items[].risk · signalsRisk and what it is based on (RECENT, MX, WEB, CORP_REGISTRAR ...).
generated · scanned · registeredCandidates generated, probed and found registered.
analyzed_atUnix time the analysis ran. With cached true, this is when the stored result was produced.
cachedTrue when a stored result was served.

Reports

POST/reportscost 1

Stores a result for 30 days and returns an unguessable id; anyone who has the id can read it. The server re-reads the result itself, so the content cannot be supplied. Limited to 20 creations per 5 minutes per IP, plus a global daily ceiling.

Parameters

NameRequiredDescription
toolyesoverview · dmarc · phishing · subdomains · takeover · lookalike · dnshealth
domainyesThe domain to check.

Request

curl -s -X POST https://certadomain.com/api/v1/reports -H "content-type: application/json" \
  -d '{"tool":"overview","domain":"example.com"}'

Response 201 (abridged)

{
  "id": "Ab3dE5gH7jKl",
  "tool": "overview",
  "domain": "example.com",
  "created_at": 1794626569,
  "expires_at": 1797218569
}

Key fields

idA 12-character id. The share URL is https://certadomain.com/r/<id>.
expires_atUnix time after which it can no longer be read.
GET/reports/{id}cost 1

Reads a stored result. An expired or unknown id is a 404.

Parameters

NameRequiredDescription
idyesThe 12-character id returned on creation.

Request

curl -s "https://certadomain.com/api/v1/reports/Ab3dE5gH7jKl"

Response 200 (abridged)

{
  "tool": "overview",
  "domain": "example.com",
  "created_at": 1794626569,
  "expires_at": 1797218569,
  "result": {
    "domain": "example.com",
    "score": 96,
    "grade": "A",
    "cached": false
  }
}

Key fields

resultThe stored result, in the shape of that tool's own response.

Other

GET/openapi.jsoncost 0

The OpenAPI 3.1 description of this API, for code generators and viewers such as Swagger UI or Redoc. Not rate limited.

Request

curl -s "https://certadomain.com/api/v1/openapi.json"

Response 200 (abridged)

{
  "openapi": "3.1.0",
  "info": { "title": "Certadomain API", "version": "1.0.0", ... },
  "paths": { ... }
}
GET/cost 0

Links to the spec and docs, and the list of paths. Not rate limited.

Request

curl -s "https://certadomain.com/api/v1/"

Response 200 (abridged)

{
  "name": "Certadomain API",
  "version": "1",
  "openapi": "https://certadomain.com/api/v1/openapi.json",
  "docs": "https://certadomain.com/en/api-docs.html",
  "endpoints": [
    "/dmarc",
    "/dnshealth",
    "/lookalike",
    "/..."
  ]
}

Rate limit

Each client IP gets 60 units per 5 minutes (a fixed window). The cost on each endpoint is what one call uses: 5 for takeover and lookalike, 1 for the rest, 0 for the index and spec.

RateLimit-LimitUnits allowed per window (60)
RateLimit-RemainingUnits left
RateLimit-ResetSeconds until the window resets
RateLimit-PolicyThe policy, as 60;w=300
Retry-AfterOn 429 only: seconds to wait
HTTP/2 429
content-type: application/problem+json
ratelimit-limit: 60
ratelimit-remaining: 0
ratelimit-reset: 142
retry-after: 142

{
  "type": "urn:certadomain:problem:rate_limited",
  "title": "Rate limit reached",
  "status": 429,
  "detail": "Too many requests from this address. Wait for Retry-After seconds.",
  "error": "rate_limited"
}

The site's edge also allows 100 requests per 5 minutes per IP; past that the CDN blocks the request. How the counts are kept is described in the Privacy Policy.

Errors

4xx and 5xx bodies are RFC 9457 application/problem+json. Branch on the stable error code.

HTTP/2 400
content-type: application/problem+json

{
  "type": "urn:certadomain:problem:invalid_domain",
  "title": "Invalid domain",
  "status": 400,
  "detail": "The value is not a valid domain name.",
  "error": "invalid_domain"
}
Statuserror
400missing_domain · invalid_domain · invalid_json · invalid_tool · invalid_id
404not_found (unknown path, expired report)
405method_not_allowed (the Allow header lists the methods)
413report_too_large
429rate_limited · report_limit
500server_error
502ct_unavailable (both CT sources failed; retry later)
503storage_unavailable

Do not read the body of a 404. The CDN replaces it with the site's HTML page. Use the status code only.

Using the results

For the limits of the results (DKIM selector coverage, subdomains absent from CT, and so on) see About. For bugs or limit requests, use Contact.