What you will build

DMARC aggregate reports tell you who sends email with your domain and whether it authenticates. Sender Audit collects them, parses them and keeps the figures per domain and per day. The API gives you the same figures as the dashboard, for example to:

  • feed a weekly report or a BI dashboard with the compliance rate of each domain;
  • alert when the share of failing messages jumps, or when a regular reporter goes silent;
  • audit a portfolio of domains: which ones still sit at p=none, which ones send their reports nowhere.

This guide goes from the summary to the detail: posture of every domain, volumes over time, then the sources of one day and the raw reports.

Endpoint reference This guide shows which calls to chain and what to read in each response. Every field is documented in the API reference, generated from the API itself.

Before you start

  • An API key created from the dashboard, under Account, Developer, on a plan that includes API access.
  • Domains covered by the account. Reports are only kept for domains verified on the account. You do not always need to verify first: when a report arrives for a new domain whose DMARC record lists your collection address, that record is the proof, and the domain is verified automatically (within your plan’s quota). To verify one explicitly, see API: monitor your domains.
  • Reports coming in. Your domain’s DMARC record must send its reports to your collection address (step 1). Count 24 to 72 hours before the first ones arrive: receivers send them once a day.
export SA_KEY="sa_live_your_key"
export SA_API="https://senderaudit.com/api/v1"
import os, requests

API = "https://senderaudit.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SA_KEY']}"

Most endpoints accept the same filters:

ParameterEffect
domainOne domain, by name: example.com
groupThe domains of one resource group, by its id
daysWindow in whole UTC days, 30 by default

Without domain or group, figures cover every domain of the account. How far back you can go depends on your plan: responses carry it as history_days.

Step 1: get the reports flowing

GET /dmarc/address returns your account’s collection address, rua_address. It goes in the rua= tag of the DMARC record published at _dmarc.example.com:

v=DMARC1; p=none; rua=mailto:your-address@rua.senderaudit.com

To check that a domain’s record really points to it, call GET /dmarc/record-check?domain=example.com. It resolves the record live and answers our_rua_present: true once the address is in place. rua_count_warning flags a record that already lists many addresses, which some receivers refuse to honour.

curl -s "$SA_API/dmarc/record-check?domain=example.com" -H "Authorization: Bearer $SA_KEY"

GET /domains/{id}/setup goes one step further: it returns the exact DMARC record to publish, completed with your address rather than replacing what is there.

Step 2: the posture of every domain

GET /dmarc/domains-summary answers “where does each domain stand?” in one call, grouped by organizational domain.

curl -s "$SA_API/dmarc/domains-summary" -H "Authorization: Bearer $SA_KEY" | jq .totals
summary = session.get(f"{API}/dmarc/domains-summary").json()
print(summary["totals"])
for group in summary["groups"]:
    for d in group["domains"]:
        print(d["domain"], d.get("policy"), d["rua_status"], d["reports_30d"])

totals counts the domains per policy (reject, quarantine, none, no_policy), and those whose reports go nowhere useful (no_rua). For each domain:

FieldWhat it is
policyPublished policy: reject, quarantine, none, or null when there is no record
inheritedtrue when the domain has no record of its own and falls under its organizational domain’s
rua_statusWhere its reports go: received (to you), via_parent (through the parent’s record), elsewhere, none, or other_path (reports arrive although DNS does not point to you)
reports_30dReports actually received over 30 days, whatever DNS says

The values come from the last monitoring cycle, not from a live DNS lookup. Use record-check to force a live check on one domain.

Step 3: the key figures

GET /dmarc/overview returns what the synthesis screen shows above the fold, for the window you ask.

curl -s "$SA_API/dmarc/overview?domain=example.com&days=30" -H "Authorization: Bearer $SA_KEY" | jq .kpis
overview = session.get(f"{API}/dmarc/overview", params={"domain": "example.com", "days": 30}).json()
kpis = overview["kpis"]
{
  "messages": 184230,
  "compliance": 0.9874,
  "spf_aligned": 0.9411,
  "dkim_aligned": 0.9802,
  "alignment_ready": true
}
  • messages: volume described by the reports over the window.
  • compliance: share of that volume that is legitimate, between 0 and 1. Identified forwarders count as legitimate: they break alignment by design, and you do not control them.
  • spf_aligned, dkim_aligned: share of the volume aligned on each mechanism. null while alignment_ready is false, which means “not computed yet”, never zero.
  • compliance is null on an empty window: no volume is not a rate of zero.

The same response breaks the volume down by sender role in buckets (legitimate, forwarder, unauthorized, unclassified…), and series gives one value per day for the main senders, ready to chart.

Step 4: volumes over time

Two endpoints, depending on whether you want the account as a whole or one line per domain.

GET /dmarc/stats: one row per day, all selected domains together.

{ "day": 20741, "date": "2026-10-14", "pass": 6120, "forwarded": 84, "fail": 37 }

GET /dmarc/daily: one row per domain and per day, with what receivers did with the messages (disp_none, disp_quarantine, disp_reject).

rows = session.get(f"{API}/dmarc/daily", params={"days": 7}).json()["rows"]
for r in rows:
    fail_rate = r["fail"] / r["total"] if r["total"] else 0
    if fail_rate > 0.05:
        print(f'{r["date"]} {r["domain"]}: {fail_rate:.1%} failing, {r["disp_reject"]} rejected')

The three counters mean:

CounterMeaning
passSPF or DKIM passed, aligned with your domain
forwardedFailed, but relayed by an identified forwarder or mailing list. Not your fault, and usually not fixable on your side
failFailed with no known excuse: a misconfigured platform of yours, or someone spoofing you

Reports arrive with a delay, mostly the next day, sometimes up to a week later. Yesterday’s figures keep growing for a few days: compare complete days.

Going further: the detail

The sources of one day

When a day looks wrong, GET /dmarc/daily/detail lists who sent for one domain on one date, aggregated by source IP.

curl -s "$SA_API/dmarc/daily/detail?domain=example.com&date=2026-10-14&status=fail&limit=50" \
  -H "Authorization: Bearer $SA_KEY"

Each source carries its IP, the name of the service behind it when known (source_display), its country, the SPF and DKIM results, the From and envelope domains, the receivers’ disposition and the message count. status filters on pass, forwarded, fail (comma separated), q searches the IP, names and domains. Without limit, the whole day comes back, which can be thousands of sources on a large mailbox domain: page with limit and offset.

Who sends on your behalf

GET /dmarc/senders returns the sender catalogue over the window, grouped by role: legitimate, forwarder, unauthorized, unclassified, no_rdns, ignored. Each sender has its volumes (total_msgs, pass_msgs, fail_msgs, pass_rate), the dispositions applied, and its first and last appearance. unclassified with a large volume is where to look first.

Classifying them is a workflow of its own, with the evidence to decide on each source: see API: classify who sends on your behalf.

Who reports

GET /dmarc/reporters lists the organisations that send you reports (Google, Microsoft, Yahoo…), with a status: active, occasional, or silent when a regular reporter has sent nothing for more than three days. A silent major receiver usually means a DNS change broke your rua=.

The raw reports

  • GET /dmarc/reports lists the reports received, filtered by domain, group or org (the reporter’s name), paged with limit (200 max) and offset. Dates are Unix timestamps.
  • GET /dmarc/reports/{report_id} returns one report with all its records, as the receiver sent it.

Domains you do not monitor yet

GET /dmarc/unmatched-domains lists domains for which reports reached your address without being covered by the account. Those reports were not kept: verify the domain to collect the next ones.

Limits and errors

SituationAnswer
Rate limit reached (20 to 60 calls per minute depending on the endpoint)429, with a Retry-After header
record-check on a domain not verified on the account403
Figures for a domain the account does not coverEmpty results, not an error
Missing or invalid key401
Plan without API access403

GET /dmarc/quota returns the month’s consumption of aggregated DMARC messages against your plan’s limit, when the plan has one.