API REFERENCE · GET · STABLE

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.

01 · OVERVIEW

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`.
02 · PARAMETERS

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.
03 · RESPONSE

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.
04 · EXAMPLE

Example response

Truncated but representative example for a real lookup at `microsoft.com`:

Sample response
{
  "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" }
      ]
    }
  }
}
05 · ERRORS

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.
06 · BEST PRACTICES

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.
07 · FAQ

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.