DMARC Policy API — /api/v1/domain/dmarc
Returns the raw DMARC string, parsed tags, effective-enforcement grade (A–F), cross-domain report authorization results, bulk-sender + BIMI compliance flags, severity-tagged validation tests, and an AI diagnosis with copy-paste TXT fix snippets.
What DMARC Policy does
Fetches and parses the DMARC TXT record at `_dmarc.<domain>`, then layers on a computed grade, cross-domain reporter authorization check, compliance derivation, and (asynchronously) an AI diagnosis.
- Method GET
- Path /api/v1/domain/dmarc
- Stability STABLE
- Version v2026-07-16
- Summary Fetch, parse, grade, and diagnose the DMARC record for a domain.
- AI analysis Returned inline when cached (`X-Analysis-Status: complete`); otherwise a pointer at `AiAnalysisMeta.id` is returned and the client polls `/api/v1/ai/analysis/status/:id`.
Request parameters
Pass these as query-string parameters. Required params marked required.
- domain (required) — string Apex or subdomain. The lookup hits `_dmarc.<domain>`.
- tests — boolean, default false Run the eleven severity-tagged validation checks. Set to `true` for the full report.
- refresh — boolean, default false Bypass the AI-analysis cache and force a fresh Gemini evaluation. Costs a fresh model call — use sparingly.
Response schema
All successful responses follow the standard `{ success: true, data: {...} }` envelope. Notable fields on the data object:
- data.found — boolean True when a valid `v=DMARC1` record was located at `_dmarc.<domain>`.
- data.domain — string The domain that was queried (org domain after normalization).
- data.source — string `domain` — record was found at the queried domain.
- data.recordCount — number Count of `v=DMARC1` records found. >1 means receivers will ignore all of them (RFC 7489).
- data.dmarc — string Raw DMARC TXT record.
- data.parsed.parsed — object Tag → value map (`v`, `p`, `sp`, `pct`, `rua`, `ruf`, `adkim`, `aspf`, `fo`, `rf`, `ri`). `pct` is a number; `rua`/`ruf` are arrays with `mailto:` stripped.
- data.parsed.descriptions — object Plain-English explanation for each tag present in the record.
- data.score — object `{grade: 'A'|'B'|'C'|'D'|'F', effectiveEnforcement: 0.0–2.0, policyStrength, pct, protectionState}`. Effective enforcement = policyScore × (pct/100).
- data.reporting.rua — array<object> Per-URI: `{uri, domain, authorized, reason, authHost?}`. `authorized=false` with `reason='no_auth_record'` means the `_report._dmarc` authorization record is missing — receivers will silently drop the reports.
- data.reporting.ruf — array<object> Same shape as rua, for forensic reporting URIs.
- data.compliance — object `{googleYahoo2024, microsoft2025, bimiEligible, hasRua}`. Booleans reflecting bulk-sender rules and BIMI eligibility.
- data.tests — array<object> Empty unless `tests=true`. Each: `{test, result, passed, severity: 'high'|'med'|'low'}`. Eleven checks including single-record, policy strength, pct rollout, external reporter authorization, alignment strictness.
- data.AiAnalysis — object | null Present when analysis is cached and complete. `{summary, quick_recommendations[], security_assessment[{title, description, severity}], fixes[{issue, kind: 'dns_txt'|'dig', host, snippet}]}`. `null` when the analysis is still being generated — poll `data.AiAnalysisMeta.id`.
- data.AiAnalysisMeta — object | null `{id, avgMs, etaMs}` — pointer used to poll `/api/v1/ai/analysis/status/:id`. Cleared once `AiAnalysis` is populated.
Example response
Truncated but representative example for a real lookup at `microsoft.com`:
{
"success": true,
"data": {
"found": true,
"domain": "microsoft.com",
"source": "domain",
"recordCount": 1,
"dmarc": "v=DMARC1; p=reject; pct=100; rua=mailto:[email protected]; sp=quarantine; adkim=r; aspf=r",
"parsed": {
"parsed": {
"v": "DMARC1",
"p": "reject",
"sp": "quarantine",
"pct": 100,
"rua": ["[email protected]"],
"adkim": "r",
"aspf": "r"
},
"descriptions": {
"p": "Reject failing messages outright.",
"sp": "Treat failing subdomain messages as suspicious.",
"pct": "100% rollout — policy applies to all failing mail.",
"rua": "Where aggregate reports are sent."
}
},
"score": {
"grade": "A",
"effectiveEnforcement": 2.0,
"policyStrength": "reject",
"pct": 100,
"protectionState": "enforcing"
},
"reporting": {
"rua": [
{
"uri": "[email protected]",
"domain": "rua.agari.com",
"authorized": true,
"reason": "authorized",
"authHost": "microsoft.com._report._dmarc.rua.agari.com"
}
],
"ruf": []
},
"compliance": {
"googleYahoo2024": true,
"microsoft2025": true,
"bimiEligible": true,
"hasRua": true
},
"tests": [
{ "test": "DMARC Record Published", "result": "DMARC record found", "passed": true, "severity": "high" },
{ "test": "Single DMARC Record", "result": "Only one DMARC record found", "passed": true, "severity": "high" },
{ "test": "Policy Strength", "result": "Enforcing (p=reject)", "passed": true, "severity": "med" },
{ "test": "Percent Rollout", "result": "Policy applies to 100% of mail", "passed": true, "severity": "low" },
{ "test": "External Reporter Authorization", "result": "All external reporters have authorization records", "passed": true, "severity": "med" }
],
"AiAnalysis": {
"summary": "microsoft.com is at p=reject with 100% rollout — full DMARC enforcement. Aggregate reports go to a properly authorized external reporter.",
"quick_recommendations": [
"Consider raising sp= from quarantine to reject once subdomain traffic is inventoried",
"Publish BIMI — you're eligible at p=reject; pct=100"
],
"security_assessment": [
{ "title": "Full DMARC enforcement", "description": "p=reject with pct=100 means all failing mail is rejected outright.", "severity": "info" }
],
"fixes": [
{ "issue": "Tighten subdomain policy to match main", "kind": "dns_txt", "host": "_dmarc.microsoft.com", "snippet": "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:[email protected]; adkim=r; aspf=r" }
]
}
}
}Error codes
Errors return HTTP status codes paired with stable machine-readable codes. Match on the code field rather than the human-readable message.
- 200 with data.found=false No DMARC record exists at `_dmarc.<domain>`. Not an error — success:true, with an explicit `found:false` payload so clients can render a 'no record' state uniformly.
- 400 INVALID_DOMAIN The `domain` parameter is missing or not a valid domain string.
- 500 UNEXPECTED_ERROR Upstream DNS lookup failed unexpectedly. Retry with backoff.
Implementation tips
Patterns we recommend when integrating this endpoint:
- Read the grade, not just the tag `data.score.grade` is the number that matters. `p=reject; pct=10` reads as grade C, not A. Alerting on tag values misses partial rollouts.
- Alert on grade regression Cron the endpoint per domain. Store the last grade. Alert when the letter drops (A → B → C). Catches 'temporary' rollbacks that never get reversed.
- Check reporter authorization on every run External reporters can lose their `_report._dmarc` authorization silently (e.g., if the reporter migrates DNS). Iterate `data.reporting.rua` and flag any entry with `authorized:false`.
- Gate CI on compliance flags Read `data.compliance` in your deploy pipeline. Hard-fail if `googleYahoo2024` or `microsoft2025` is false on any bulk-sending domain.
- Handle the async AI analysis When `AiAnalysis` is null and `AiAnalysisMeta.id` is present, poll `/api/v1/ai/analysis/status/:id` with the `avgMs` hint. Do not fail the response on missing AI — the structured fields are complete without it.
- Aim for p=reject; pct=100 in production Grade A. Full enforcement. BIMI eligible. Anything else is a stop, not a destination.
Frequently asked questions
- How is the grade computed? A = `p=reject; pct=100; rua=` set. B = `p=quarantine; pct=100; rua=` set. C = `p=quarantine`/`reject` with `pct<100`. D = `p=none` with `rua=`. F = no record, invalid syntax, multiple records, or `p=none` without `rua=`. Effective enforcement = `policyScore × (pct/100)`, range 0.0–2.0.
- Why is pct important? Lets you ramp policy enforcement gradually. Start at `pct=10`, watch reports, raise over weeks. Trap: teams start at `pct=10` for testing and forget to raise it — reads as reject but only enforces on 10%.
- What does data.reporting.rua[].authorized mean? For external reporters (reporter domain differs from published domain), RFC 7489 § 7.1 requires a `<yourdomain>._report._dmarc.<reporter>` TXT record. `authorized:true` means the record is present and valid; `false` means receivers will silently discard your aggregate reports.
- Do I always need to pass tests=true? No — omit it if you only need the raw record and parsed tags. Pass `tests=true` for the eleven severity-tagged checks. Score, reporting, and compliance are returned unconditionally.