Reports API
Access DMARC aggregate report data programmatically: individual reports with their per-source records, rolled-up statistics, file upload, and CSV export.
Retention shapes every response
Reports older than your plan's retention window are filtered out server-side, and any days parameter you send is silently capped to it. Monitor keeps 30 days and is additionally limited to the 10 most recent reports with no pagination; Protect keeps 90, Growth 180, Professional and above 365. An empty array often means "outside retention", not "no data".
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/reports | List DMARC aggregate reports |
| GET | /api/v1/reports/{report_id} | Get report details |
| GET | /api/v1/reports/stats/summary | Get aggregated report statistics |
| POST | /api/v1/reports/upload | Upload a DMARC report file |
| GET | /api/v1/reports/export/csv | Export report records as CSV |
List DMARC Reports
Returns reports for your organization, most recently ingested first, each with its full records array.
GET /api/v1/reportscurl -X GET "https://api.ddmarc.com/api/v1/reports?domain_id=42&limit=50&offset=0" \
-H "X-API-Key: $DDMARC_API_KEY"Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| domain_id | integer | all | Restrict to one domain. |
| limit | integer | 50 | Rows to return. Hard ceiling of 200. |
| offset | integer | 0 | Rows to skip. |
There are no date-range parameters on this endpoint. To bound a window, use /reports/stats/summary?days= or the CSV export, both of which accept days.
[
{
"id": 9012,
"domain_id": 42,
"report_id": "16058034545453596399",
"org_name": "google.com",
"date_range_begin": "2026-07-17T00:00:00Z",
"date_range_end": "2026-07-17T23:59:59Z",
"policy_domain": "example.com",
"policy_adkim": "r",
"policy_aspf": "r",
"policy_p": "reject",
"policy_sp": null,
"policy_pct": 100,
"created_at": "2026-07-18T09:04:11Z",
"records": [
{
"id": 551201,
"source_ip": "209.85.220.41",
"count": 843,
"disposition": "none",
"dkim_result": "pass",
"spf_result": "pass",
"header_from": "example.com",
"envelope_from": "example.com",
"envelope_to": null
}
]
}
]Get a Single Report
Returns one report with all of its records, in the same shape as an element of the list response.
GET /api/v1/reports/{report_id}curl -X GET "https://api.ddmarc.com/api/v1/reports/9012" \
-H "X-API-Key: $DDMARC_API_KEY"{report_id} here is the integer idfield, not the reporter's report_id string.
Summary Statistics
Rolls up volume and pass rates over a time window, without you having to page through every report.
GET /api/v1/reports/stats/summarycurl -X GET "https://api.ddmarc.com/api/v1/reports/stats/summary?domain_id=42&days=30" \
-H "X-API-Key: $DDMARC_API_KEY"Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| domain_id | integer | all | Restrict to one domain. |
| days | integer | 30 | Look-back window, 1 to 365. Capped to your plan's retention. |
{
"total_reports": 184,
"total_emails": 412903,
"pass_rate": 98.4,
"dkim_pass_rate": 99.1,
"spf_pass_rate": 97.2,
"top_sources": [
{ "source_ip": "209.85.220.41", "total": 180422 }
]
}Rates are percentages rounded to one decimal place. A message counts as passing only when both DKIM and SPF pass.
Upload a Report File
Ingests a DMARC aggregate report you already hold as a file. Accepts raw XML, ZIP or GZIP as a multipart upload, up to 10 MB. Requires a key with write scope, and is limited to 30 uploads per minute.
POST /api/v1/reports/uploadcurl -X POST "https://api.ddmarc.com/api/v1/reports/upload" \
-H "X-API-Key: $DDMARC_API_KEY" \
-F "file=@google.com!example.com!1752710400!1752796799.xml.gz"{
"message": "1 report(s) uploaded successfully",
"uploaded": [
{
"report_id": "16058034545453596399",
"domain": "example.com",
"org_name": "google.com",
"records_count": 12,
"total_emails": 1843
}
],
"duplicates_skipped": 0,
"no_domain_skipped": 0
}If nothing lands, the call fails instead of returning an empty result: 409 when every report in the file was already ingested, and 400when the report's domain is not in your organization — add the domain first.
CSV Export
Export is a dedicated endpoint, not a format=csv parameter on other routes. It streams one row per DMARC record with the columns Date, Domain, Reporter, Source IP, Country, Organization, Count, Disposition, DKIM Result, SPF Result, Header From and Envelope From.
GET /api/v1/reports/export/csvcurl -X GET "https://api.ddmarc.com/api/v1/reports/export/csv?domain_id=42&days=30" \
-H "X-API-Key: $DDMARC_API_KEY" \
-o dmarc-records.csvRequires the Protect plan or higher. Limited to 10 exports per hour and capped at 500,000 rows per call — narrow days or domain_id if you are near that. Senders and the activity log have their own exports at /api/v1/senders/export/csv and /api/v1/activity/export/csv.
Report Object
| Field | Type | Description |
|---|---|---|
| id | integer | Report identifier, used as {report_id} in paths |
| domain_id | integer | Domain this report belongs to |
| report_id | string | The reporter's own report id, from the XML |
| org_name | string | Reporting organization, e.g. google.com |
| date_range_begin | string | ISO 8601 start of the reporting window |
| date_range_end | string | ISO 8601 end of the reporting window |
| policy_domain | string | Domain the published policy was found on |
| policy_p | string | null | Published policy: none, quarantine or reject |
| policy_sp | string | null | Subdomain policy, if published |
| policy_adkim | string | null | DKIM alignment mode: r (relaxed) or s (strict) |
| policy_aspf | string | null | SPF alignment mode: r (relaxed) or s (strict) |
| policy_pct | integer | null | Percentage of mail the policy applied to |
| created_at | string | ISO 8601 ingestion timestamp |
| records | array | Per-source results; see the record object below |
A report carries no pre-computed totals or pass rate. Sum records[].count yourself, or use the summary endpoint.
Record Object
| Field | Type | Description |
|---|---|---|
| id | integer | Record identifier |
| source_ip | string | Sending IP address |
| count | integer | Messages this row represents |
| disposition | string | null | What the receiver did: none, quarantine or reject |
| dkim_result | string | null | DKIM evaluation: pass, fail, none, and so on |
| spf_result | string | null | SPF evaluation: pass, fail, none, and so on |
| header_from | string | null | Domain in the From header |
| envelope_from | string | null | MAIL FROM domain, when the reporter supplies it |
| envelope_to | string | null | RCPT TO domain, when the reporter supplies it |
Analysing Failures
Raw reports tell you what happened; the failure-analysis endpoints tell you why. They live under their own prefix and each accept days(1–365, default 30) and an optional domain_id.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/failure-analysis/summary | Get failure totals and split by mechanism |
| GET | /api/v1/failure-analysis/root-causes | Get classified root causes with fix suggestions |
| GET | /api/v1/failure-analysis/root-causes/by-source | Get root causes grouped by sending source |
| GET | /api/v1/failure-analysis/sources | List failing sources |
| GET | /api/v1/failure-analysis/spoof-clusters | Get suspected spoofing campaign clusters |
| GET | /api/v1/failure-analysis/by-header-from | Get failures grouped by spoofed From identity |
| GET | /api/v1/failure-analysis/trend | Get failure trend over time |
| GET | /api/v1/failure-analysis/resolution-stats | Get time-to-resolution statistics |
{
"total_failures": 6604,
"total_emails": 412903,
"failure_rate": 1.6,
"period_days": 30,
"by_reason": [
{
"reason": "spf_only",
"display_name": "SPF Only Failed",
"count": 4102,
"percentage": 62.1,
"description": "SPF authentication failed but DKIM passed. Check SPF records and authorized senders."
}
]
}Looking for charts and breakdowns?
Trend lines, geographic distribution, volume heatmaps, top sources, protocol breakdown and the compliance scorecard are served from /api/v1/dashboard/*, not from the reports endpoints. Per-source detail lives under /api/v1/senders. Both are listed on the API overview.