GET/api/v2/dmarc
DMARC check API
The DMARC policy receivers apply to a domain: the record found (or inherited), every tag's effective value, where reports go and whether those addresses accept them, and how strong the policy is.
- 20 calls a minute per IP
- Same engine as the DMARC Checker tool
{
"query": {
"domain": "github.com"
},
"state": "enforced",
"stateRfc7489": "enforced",
"record": "v=DMARC1; p=quarantine; sp=reject; pct=100; rua=mailto:dmarc@github.com; ruf=mailto:dmarc@github.com; fo=1",
"foundAt": "github.com",
"source": "at_name",
"inherited": false,
"organizationalDomain": "github.com",
"ttl": 263,
"cname": null,
"policy": {
"tag": "p",
"requested": "quarantine",
"applied": "quarantine"
},
"tags": {
"p": "quarantine",
"sp": "reject",
"np": null,
"adkim": "r",
"aspf": "r",
"testing": false,
"pct": 100,
"fo": [
"1"
],
"psd": "u"
},
"outcomes": {
"domain": {
"action": "policy",
"tag": "p",
"requested": "quarantine",
"applied": "quarantine",
"at": "github.com"
},
"subdomains": {
"action": "policy",
"tag": "sp",
"requested": "reject",
"applied": "reject",
"at": "github.com"
},
"nonExistent": {
"action": "policy",
"tag": "sp",
"requested": "reject",
"applied": "reject",
"at": "github.com"
}
},
"reports": [
{
"type": "aggregate",
"uri": "mailto:dmarc@github.com",
"address": "dmarc@github.com",
"host": "github.com",
"external": false,
"authorization": "internal",
"processor": {
"id": "github",
"name": "GitHub"
},
"acceptsMail": true
},
{
"type": "failure",
"uri": "mailto:dmarc@github.com",
"address": "dmarc@github.com",
"host": "github.com",
"external": false,
"authorization": "internal",
"processor": {
"id": "github",
"name": "GitHub"
},
"acceptsMail": true
}
],
"issues": [
{
"severity": "info",
"code": "pct_removed",
"message": "pct=100 was removed in RFC 9989: harmless here."
},
{
"severity": "info",
"code": "ruf_present",
"message": "1 failure report address (ruf): Gmail and Microsoft never send failure reports, and others may include message headers."
}
],
"tookMs": 298
}How it works
Discovers the policy as an RFC 9989 (DMARCbis) receiver does, _dmarc.<domain> and then the DNS tree walk up to the organizational domain, and as a receiver still on RFC 7489 does (the Public Suffix List), side by side: state and stateRfc7489 can differ. The record is parsed tag by tag with defaults filled in, policy says what receivers apply to this domain (after t=y), and outcomes what they do for its subdomains and for names under it that don't exist.
Every rua and ruf address is checked as RFC 9990 §4 has receivers check it: an address outside the domain's organization needs <domain>._report._dmarc.<host> published by the destination, or receivers ignore it; each destination's MX says whether it can take mail at all.
Asks Google Public DNS (Cloudflare's 1.1.1.1 when Google fails) and keeps every question's outcome: a lookup that fails is "couldn't check" (incomplete), never "no record".
Query parameters
domainstringrequiredThe domain whose DMARC policy to check (the From domain of the mail). A URL (its host), an email address (its domain), an IDN in Unicode or a trailing dot are read the way the tool page reads them; the normalised name comes back inquery.domain.
Response fields
200 with this JSON object, plus tookMs, and incomplete with warnings when a source failed. Fields marked optional appear only when they apply.
queryobjectalwaysWhat was looked up, normalised (lower case, no trailing dot, IDNs as Punycode).domainstringalwaysThe domain checked (the RFC5322.From domain), lower case, IDNs as Punycode.
statestringalwaysHow strong the policy is for RFC 9989 receivers (DMARCbis):enforced(quarantine or reject, applied as written),weak(enforced on paper, but t=y, pct below 100, or sp/np=none at the organizational domain),monitoring(p=none),broken(a record receivers can't use as written),missing(no record at the name or any ancestor),couldnt_check(the lookups that decide it failed: never read as missing).one of
"enforced","weak","monitoring","broken","missing","couldnt_check"stateRfc7489stringalwaysThe same reading for receivers still on RFC 7489 (Public Suffix List, pct, no t/np/psd); it can differ.one of
"enforced","weak","monitoring","broken","missing","couldnt_check"recordstring or nullalwaysThe record that applies, as published (TXT strings joined);nullwhen none applies.foundAtstring or nullalwaysThe name it was found at (_dmarc.+ this): the domain itself, or an ancestor.sourcestring or nullalwaysHow it was found:at_name(the domain's own),tree_walk(an ancestor's, by RFC 9989's DNS tree walk),psl_org(the Public Suffix List's organizational domain, RFC 7489 only).one of
"at_name","tree_walk","psl_org",nullinheritedbooleanalwaysThe record is an ancestor's: its sp (or np) decides for this domain.organizationalDomainstring or nullalwaysThe organizational domain by RFC 9989's DNS tree walk.ttlinteger or nullalwaysThe record's TTL in seconds.cnamearray of strings or nullalwaysWhen_dmarc.<foundAt>is an alias: each CNAME hop's target, in order (whoever controls the last one controls the policy);nullotherwise.policyobject or nullalwaysThe policy receivers apply to mail from this domain;nullwhen no usable record applies.tagstringalwaysThe tag that decides: p for the domain's own record, sp (or np for a name that doesn't exist) for an inherited one.one of
"p","sp","np"requestedstringalwaysThe policy the record asks for.one of
"none","quarantine","reject"appliedstringalwaysWhat RFC 9989 receivers apply: one level lower under t=y.one of
"none","quarantine","reject"
tagsobject or nullalwaysEvery tag's effective value, defaults filled in;nullwhen no record applies.Show 9 fields
pstring or nullalwaysp as published and valid;nullwhen absent or invalid.one of
"none","quarantine","reject",nullspstring or nullalwayssp (subdomains) as published and valid;nullwhen absent (p applies).one of
"none","quarantine","reject",nullnpstring or nullalwaysnp (names that don't exist, RFC 9989) as published and valid;nullwhen absent.one of
"none","quarantine","reject",nulladkimstringalwaysDKIM alignment:rrelaxed (the default),sstrict.one of
"r","s"aspfstringalwaysSPF alignment:rrelaxed (the default),sstrict.one of
"r","s"testingbooleanalwayst=y: RFC 9989's test mode (receivers apply one level lower).pctinteger or nullalwayspct, when published and valid: removed by RFC 9989; RFC 7489 receivers apply the policy to that share of failing mail.foarray of stringsalwaysfo: when failure reports are asked for (default0).psdstringalwayspsd:ya public suffix operator's record,nan organizational domain,uunspecified (the default).one of
"y","n","u"
outcomesobject or nullalwaysWhat RFC 9989 receivers do with mail that fails DMARC, for the domain, its existing subdomains and names under it that don't exist;nullwhen the input wasn't a domain.domainobjectalwaysWhat an RFC 9989 receiver does with failing mail from the domain itself.Show 5 fields
actionstringalwayspolicy(a policy applies),no_dmarc(no usable record: receivers apply no DMARC),unknown(a lookup that decides it failed, or the record repeats a tag).one of
"policy","no_dmarc","unknown"tagstring or nullalwaysThe tag that decides;nullunlessactionispolicy.one of
"p","sp","np",nullrequestedstring or nullalwaysThe policy the record asks for;nullunlessactionispolicy.one of
"none","quarantine","reject",nullappliedstring or nullalwaysWhat receivers apply: one level lower than requested under t=y;nullunlessactionispolicy.one of
"none","quarantine","reject",nullatstring or nullalwaysThe name whose record decides (the DMARC policy domain).
subdomainsobjectalwaysWhat an RFC 9989 receiver does with failing mail from its existing subdomains.Show 5 fields
actionstringalwayspolicy(a policy applies),no_dmarc(no usable record: receivers apply no DMARC),unknown(a lookup that decides it failed, or the record repeats a tag).one of
"policy","no_dmarc","unknown"tagstring or nullalwaysThe tag that decides;nullunlessactionispolicy.one of
"p","sp","np",nullrequestedstring or nullalwaysThe policy the record asks for;nullunlessactionispolicy.one of
"none","quarantine","reject",nullappliedstring or nullalwaysWhat receivers apply: one level lower than requested under t=y;nullunlessactionispolicy.one of
"none","quarantine","reject",nullatstring or nullalwaysThe name whose record decides (the DMARC policy domain).
nonExistentobjectalwaysWhat an RFC 9989 receiver does with failing mail from names under it that don't exist.Show 5 fields
actionstringalwayspolicy(a policy applies),no_dmarc(no usable record: receivers apply no DMARC),unknown(a lookup that decides it failed, or the record repeats a tag).one of
"policy","no_dmarc","unknown"tagstring or nullalwaysThe tag that decides;nullunlessactionispolicy.one of
"p","sp","np",nullrequestedstring or nullalwaysThe policy the record asks for;nullunlessactionispolicy.one of
"none","quarantine","reject",nullappliedstring or nullalwaysWhat receivers apply: one level lower than requested under t=y;nullunlessactionispolicy.one of
"none","quarantine","reject",nullatstring or nullalwaysThe name whose record decides (the DMARC policy domain).
reportsarray of objectsalwaysEvery usable rua (aggregate) and ruf (failure) address, and whether receivers will send to it (RFC 9990 §4, RFC 9991 §5).Show 8 fields
typestringalwaysrua (aggregate) or ruf (failure).one of
"aggregate","failure"uristringalwaysThe URI as written, e.g.mailto:dmarc@example.com.addressstringalwaysThe mail address.hoststringalwaysThe address's domain.externalboolean or nullalwaysThe address is outside the policy's organizational domain, so it needs the destination's authorization;nullwhen that couldn't be decided.authorizationstringalwaysinternal(same organization: none needed),authorized(the destination publishes<domain>._report._dmarc.<host>),unauthorized(it doesn't: receivers ignore the address),unknown(the check failed),too_long(the authorization name exceeds DNS limits),override_bad(the destination redirects to another host: no reports),skipped(past the first 10 hosts).one of
"internal","authorized","unauthorized","unknown","too_long","override_bad","skipped"processorobject or nullalwaysThe DMARC report service or company behind the address, when known.Show 2 fields
idstringalwaysOur stable id for the company, e.g.google,microsoft365,sendgrid.namestringalwaysIts name as it writes it, e.g.Google Workspace.
acceptsMailboolean or nullalwaysThe address's domain can receive mail (MX, or an address as implicit MX);falsefor NXDOMAIN, no MX or a null MX;nullwhen unchecked.
issuesarray of objectsalwaysThe DMARC Checker's findings (RFC 9989, RFC 9990, RFC 9991, RFC 7489), worst first; good news (a reject policy, an authorized destination) is left out.errorbreaks something (receivers fail it),warningrisks it,infois worth knowing.severitystringalwayserrorbreaks something,warningrisks it,infois worth knowing.one of
"error","warning","info"codestringalwaysStable machine-readable code.messagestringalwaysThe problem and what to do about it, in plain English.
Errors
429rate_limitedThe domain (its registrable domain) was checked 30 times in the last minute by all callers together, or every caller together reached 600 a minute: retry afterRetry-Afterseconds.
Plus the errors every endpoint can return: 400 for a missing or unreadable parameter, 429 past the limit, 503 when every source failed.
Code samples
curl "https://dnsrobot.net/api/v2/dmarc?domain=github.com"const params = new URLSearchParams({ domain: "github.com" });
const res = await fetch(`https://dnsrobot.net/api/v2/dmarc?${params}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.policy);import requests
r = requests.get(
"https://dnsrobot.net/api/v2/dmarc",
params={"domain": "github.com"},
timeout=30,
)
data = r.json()
if not r.ok:
raise RuntimeError(data["error"]["message"])
print(data["policy"])package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
q := url.Values{"domain": {"github.com"}}
res, err := http.Get("https://dnsrobot.net/api/v2/dmarc?" + q.Encode())
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(data["error"])
}
fmt.Println(data["policy"])
}<?php
$query = http_build_query(["domain" => "github.com"]);
$context = stream_context_create(["http" => ["ignore_errors" => true, "timeout" => 30]]);
$data = json_decode(file_get_contents("https://dnsrobot.net/api/v2/dmarc?" . $query, false, $context), true);
if (isset($data["error"])) {
throw new RuntimeException($data["error"]["message"]);
}
print_r($data["policy"]);require "net/http"
require "json"
uri = URI("https://dnsrobot.net/api/v2/dmarc")
uri.query = URI.encode_www_form(domain: "github.com")
data = JSON.parse(Net::HTTP.get(uri))
raise data["error"]["message"] if data["error"]
puts data["policy"]// Cargo.toml: reqwest = { version = "0.13", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let res = reqwest::blocking::get("https://dnsrobot.net/api/v2/dmarc?domain=github.com")?;
let ok = res.status().is_success();
let data: serde_json::Value = res.json()?;
if !ok {
return Err(data["error"]["message"].to_string().into());
}
println!("{}", data["policy"]);
Ok(())
}using System.Text.Json;
using var http = new HttpClient();
var res = await http.GetAsync("https://dnsrobot.net/api/v2/dmarc?domain=github.com");
var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
if (!res.IsSuccessStatusCode)
throw new Exception(data.GetProperty("error").GetProperty("message").GetString());
Console.WriteLine(data.GetProperty("policy"));github.com's policy
GET /api/v2/dmarc?domain=github.com{
"query": {
"domain": "github.com"
},
"state": "enforced",
"stateRfc7489": "enforced",
"record": "v=DMARC1; p=quarantine; sp=reject; pct=100; rua=mailto:dmarc@github.com; ruf=mailto:dmarc@github…",
"foundAt": "github.com",
"source": "at_name",
"inherited": false,
"organizationalDomain": "github.com",
"ttl": 263,
"cname": null,
"policy": {
"tag": "p",
"requested": "quarantine",
"applied": "quarantine"
},
"tags": {
"p": "quarantine",
"sp": "reject",
"np": null,
"adkim": "r",
"aspf": "r",
"testing": false,
"pct": 100,
"fo": [
"1"
],
"psd": "u"
},
"outcomes": {
"domain": {
"action": "policy",
"tag": "p",
"requested": "quarantine",
"applied": "quarantine",
"at": "github.com"
},
"subdomains": {
"action": "policy",
"tag": "sp",
"requested": "reject",
"applied": "reject",
"at": "github.com"
},
"nonExistent": {
"action": "policy",
"tag": "sp",
"requested": "reject",
"applied": "reject",
"at": "github.com"
}
},
"reports": [
{
"type": "aggregate",
"uri": "mailto:dmarc@github.com",
"address": "dmarc@github.com",
"host": "github.com",
"external": false,
"authorization": "internal",
"processor": {
"id": "github",
"name": "GitHub"
},
"acceptsMail": true
},
{
"type": "failure",
"uri": "mailto:dmarc@github.com",
"address": "dmarc@github.com",
"host": "github.com",
"external": false,
"authorization": "internal",
"processor": {
"id": "github",
"name": "GitHub"
},
"acceptsMail": true
}
],
"issues": [
{
"severity": "info",
"code": "pct_removed",
"message": "pct=100 was removed in RFC 9989: harmless here."
},
{
"severity": "info",
"code": "ruf_present",
"message": "1 failure report address (ruf): Gmail and Microsoft never send failure reports, and others may i…"
}
],
"tookMs": 298
}Shortened for the page: long lists show their first items.
google.com's policy
GET /api/v2/dmarc?domain=google.com{
"query": {
"domain": "google.com"
},
"state": "enforced",
"stateRfc7489": "enforced",
"record": "v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com",
"foundAt": "google.com",
"source": "at_name",
"inherited": false,
"organizationalDomain": "google.com",
"ttl": 300,
"cname": null,
"policy": {
"tag": "p",
"requested": "reject",
"applied": "reject"
},
"tags": {
"p": "reject",
"sp": null,
"np": null,
"adkim": "r",
"aspf": "r",
"testing": false,
"pct": null,
"fo": [
"0"
],
"psd": "u"
},
"outcomes": {
"domain": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "google.com"
},
"subdomains": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "google.com"
},
"nonExistent": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "google.com"
}
},
"reports": [
{
"type": "aggregate",
"uri": "mailto:mailauth-reports@google.com",
"address": "mailauth-reports@google.com",
"host": "google.com",
"external": false,
"authorization": "internal",
"processor": {
"id": "google",
"name": "Google"
},
"acceptsMail": true
}
],
"issues": [],
"tookMs": 39
}wikipedia.org's policy
GET /api/v2/dmarc?domain=wikipedia.org{
"query": {
"domain": "wikipedia.org"
},
"state": "enforced",
"stateRfc7489": "enforced",
"record": "v=DMARC1; p=reject; rua=mailto:dmarc-rua@wikimedia.org;",
"foundAt": "wikipedia.org",
"source": "at_name",
"inherited": false,
"organizationalDomain": "wikipedia.org",
"ttl": 600,
"cname": null,
"policy": {
"tag": "p",
"requested": "reject",
"applied": "reject"
},
"tags": {
"p": "reject",
"sp": null,
"np": null,
"adkim": "r",
"aspf": "r",
"testing": false,
"pct": null,
"fo": [
"0"
],
"psd": "u"
},
"outcomes": {
"domain": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "wikipedia.org"
},
"subdomains": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "wikipedia.org"
},
"nonExistent": {
"action": "policy",
"tag": "p",
"requested": "reject",
"applied": "reject",
"at": "wikipedia.org"
}
},
"reports": [
{
"type": "aggregate",
"uri": "mailto:dmarc-rua@wikimedia.org",
"address": "dmarc-rua@wikimedia.org",
"host": "wikimedia.org",
"external": true,
"authorization": "authorized",
"processor": {
"id": "wikimedia",
"name": "Wikimedia"
},
"acceptsMail": true
}
],
"issues": [],
"tookMs": 317
}