Documentation

Learn how to integrate and use DDMARC.

REST API

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

MethodEndpointDescription
GET/api/v1/reportsList DMARC aggregate reports
GET/api/v1/reports/{report_id}Get report details
GET/api/v1/reports/stats/summaryGet aggregated report statistics
POST/api/v1/reports/uploadUpload a DMARC report file
GET/api/v1/reports/export/csvExport report records as CSV
GET

List DMARC Reports

Returns reports for your organization, most recently ingested first, each with its full records array.

GET /api/v1/reports
curl -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

ParameterTypeDefaultDescription
domain_idintegerallRestrict to one domain.
limitinteger50Rows to return. Hard ceiling of 200.
offsetinteger0Rows 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.

Response200 OK
[
  {
    "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

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.

GET

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/summary
curl -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

ParameterTypeDefaultDescription
domain_idintegerallRestrict to one domain.
daysinteger30Look-back window, 1 to 365. Capped to your plan's retention.
Response200 OK
{
  "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.

POST

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/upload
curl -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"
Response200 OK
{
  "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/csv
curl -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.csv

Requires 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

FieldTypeDescription
idintegerReport identifier, used as {report_id} in paths
domain_idintegerDomain this report belongs to
report_idstringThe reporter's own report id, from the XML
org_namestringReporting organization, e.g. google.com
date_range_beginstringISO 8601 start of the reporting window
date_range_endstringISO 8601 end of the reporting window
policy_domainstringDomain the published policy was found on
policy_pstring | nullPublished policy: none, quarantine or reject
policy_spstring | nullSubdomain policy, if published
policy_adkimstring | nullDKIM alignment mode: r (relaxed) or s (strict)
policy_aspfstring | nullSPF alignment mode: r (relaxed) or s (strict)
policy_pctinteger | nullPercentage of mail the policy applied to
created_atstringISO 8601 ingestion timestamp
recordsarrayPer-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

FieldTypeDescription
idintegerRecord identifier
source_ipstringSending IP address
countintegerMessages this row represents
dispositionstring | nullWhat the receiver did: none, quarantine or reject
dkim_resultstring | nullDKIM evaluation: pass, fail, none, and so on
spf_resultstring | nullSPF evaluation: pass, fail, none, and so on
header_fromstring | nullDomain in the From header
envelope_fromstring | nullMAIL FROM domain, when the reporter supplies it
envelope_tostring | nullRCPT 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.

MethodEndpointDescription
GET/api/v1/failure-analysis/summaryGet failure totals and split by mechanism
GET/api/v1/failure-analysis/root-causesGet classified root causes with fix suggestions
GET/api/v1/failure-analysis/root-causes/by-sourceGet root causes grouped by sending source
GET/api/v1/failure-analysis/sourcesList failing sources
GET/api/v1/failure-analysis/spoof-clustersGet suspected spoofing campaign clusters
GET/api/v1/failure-analysis/by-header-fromGet failures grouped by spoofed From identity
GET/api/v1/failure-analysis/trendGet failure trend over time
GET/api/v1/failure-analysis/resolution-statsGet time-to-resolution statistics
GET /api/v1/failure-analysis/summary200 OK
{
  "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.

Continue to