# DMARC reports API

> Read aggregate and forensic DMARC reports for a sending domain, or upload your own.

Base URL: `https://api.emailit.com/v2`. Authenticate with `Authorization: Bearer <API key>`.

## List aggregate reports — GET /domains/{id}/dmarc/reports

> List the DMARC reports Emailit received or you uploaded for a sending domain, filtered by type, status, reporter and date.

# List aggregate reports

Returns the DMARC reports for a sending domain, newest first. Requires an API key with the `full` scope.

Emailit collects reports only for domains with DMARC reports turned on (`dmarc_reports: true` in [Update a domain](/docs/api-reference/domains/update/)), which requires the Pro plan or higher. See [DMARC reports](/docs/dmarc/reports/).

The list includes forensic reports too. Pass `type=aggregate` to list only aggregate reports, or use [List forensic reports](/docs/api-reference/dmarc/forensic/) for forensic details.

`GET /domains/{id}/dmarc/reports`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name, for example `acme.com`.

## Query parameters

- `type` (string): `aggregate` or `forensic`.

- `status` (string): Processing status: `pending`, `processed`, `failed` or `duplicate`.

- `org_name` (string): Exact name of the reporting organization, for example `google.com`.

- `from` (string): Only reports whose period starts on or after this date or date-time, for example `2026-09-01`.

- `to` (string): Only reports whose period starts on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Reports per page, up to 100.

- `offset` (integer): Number of reports to skip.

- `match`, `order`, `direction`: see [Filtering](https://emailit.com/docs/api-reference/filtering/).

The generic `key.condition=value` filters also work on `type`, `status`, `org_name` and `created_at`; those are also the sort keys for `order`. See [Filtering](/docs/api-reference/filtering/).

## Returns

Returns `data`, an array of report objects, and `meta` with `total`, `limit` and `offset`.

- `id` (string): Report ID, prefixed `dmr_`.

- `type` (string | null): `aggregate` or `forensic`. `null` until an uploaded report is processed.

- `source` (string): `smtp` for reports Emailit received at the domain's reporting address, `upload` for reports you [uploaded](/docs/api-reference/dmarc/upload/).

- `status` (string): `pending`, `processed`, `failed` (see `error_message`) or `duplicate` (the same report was already processed).

- `org_name, org_email` (string | null): The organization that sent the report and its contact address.

- `external_report_id` (string | null): The reporter's own report ID.

- `date_range_begin, date_range_end` (string | null): The period the report covers, in UTC.

- `policy_domain, adkim, aspf, p, sp, pct, fo` (string | integer | null): The DMARC policy the reporter found for your domain: alignment modes (`r` or `s`), policy and subdomain policy (`none`, `quarantine`, `reject`), percentage and failure-reporting options.

- `original_filename` (string | null): File name of an uploaded report.

- `envelope_to` (string | null): The reporting address the report was sent to.

- `created_at, updated_at, processed_at` (string | null): Timestamps in UTC.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/reports`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/reports \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d type=aggregate \
  -d from=2026-09-01 \
  -d limit=50
```

**Node.js**

```javascript
const params = new URLSearchParams({ type: 'aggregate', from: '2026-09-01', limit: '50' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/reports?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: reports, meta } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/reports",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"type": "aggregate", "from": "2026-09-01", "limit": 50},
)
reports = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/reports', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['type' => 'aggregate', 'from' => '2026-09-01', 'limit' => 50],
]);
$reports = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "object": "dmarc_report",
      "id": "dmr_3Xu63kEs8KCiyOZ1TDgnttppitx",
      "type": "aggregate",
      "source": "smtp",
      "status": "processed",
      "org_name": "google.com",
      "org_email": "noreply-dmarc-support@google.com",
      "external_report_id": "4129847120347812934",
      "date_range_begin": "2026-09-29 00:00:00+00",
      "date_range_end": "2026-09-29 23:59:59+00",
      "policy_domain": "acme.com",
      "adkim": "r",
      "aspf": "r",
      "p": "none",
      "sp": "none",
      "pct": 100,
      "fo": null,
      "original_filename": null,
      "error_message": null,
      "envelope_to": "k2v9x4qa7m@dmarc.emailitmail.com",
      "created_at": "2026-09-30 04:12:09.214377+00",
      "updated_at": "2026-09-30 04:12:11.902154+00",
      "processed_at": "2026-09-30 04:12:11.902154+00"
    }
  ],
  "meta": { "total": 1, "limit": 50, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/list/

## Retrieve a report — GET /domains/{id}/dmarc/reports/{report_id}

> Retrieve one DMARC report for a domain. Aggregate reports include their records: source IPs, volumes and SPF and DKIM results.

# Retrieve a report

Retrieves one DMARC report. For an aggregate report, the response includes its records, one per sending source, sorted by message count. For a forensic report, it includes the failure sample in `forensic`. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/reports/{report_id}`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

- `report_id` (string, required): The report ID (`dmr_…`).

## Query parameters

- `limit` (integer): Records to return, up to 500. Aggregate reports only.

- `offset` (integer): Records to skip. Aggregate reports only.

## Returns

Returns the report object (see [List aggregate reports](/docs/api-reference/dmarc/list/)) plus:

- `records` (object[]): Aggregate reports only. One entry per source IP and result combination. See the fields below.

- `meta` (object): Aggregate reports only. `total` records, `limit` and `offset`.

- `forensic` (object | null): Forensic reports only. The same fields as [Retrieve a forensic report](/docs/api-reference/dmarc/forensic-get/).

Each record has:

- `source_ip` (string): IP address that sent the messages.

- `count` (integer): Number of messages from this source with these results.

- `country_code, country_name, continent_code, asn, as_org, latitude, longitude` (string | integer | number | null): Location and network of the source IP. `null` when the IP couldn't be located.

- `disposition` (string): What the receiver did: `none`, `quarantine` or `reject`.

- `dkim, spf` (string): DMARC-aligned results as evaluated by the receiver: `pass` or `fail`.

- `header_from, envelope_from, envelope_to` (string | null): Identifiers from the report.

- `dkim_domain, dkim_selector, dkim_result` (string | null): The DKIM signature the receiver checked and its raw result.

- `spf_domain, spf_result` (string | null): The SPF domain the receiver checked and its raw result.

- `reason_type, reason_comment` (string | null): Policy override reason, for example `forwarded` or `mailing_list`.

Returns `404` if the domain or the report doesn't exist.

**Request** `GET /domains/{id}/dmarc/reports/{report_id}`

**cURL**

```bash
curl https://api.emailit.com/v2/domains/acme.com/dmarc/reports/dmr_3Xu63kEs8KCiyOZ1TDgnttppitx \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch(
  'https://api.emailit.com/v2/domains/acme.com/dmarc/reports/dmr_3Xu63kEs8KCiyOZ1TDgnttppitx',
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const report = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/reports/dmr_3Xu63kEs8KCiyOZ1TDgnttppitx",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
report = r.json()
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/reports/dmr_3Xu63kEs8KCiyOZ1TDgnttppitx', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$report = json_decode($response->getBody(), true);
```

**200**

```json
{
  "object": "dmarc_report",
  "id": "dmr_3Xu63kEs8KCiyOZ1TDgnttppitx",
  "type": "aggregate",
  "source": "smtp",
  "status": "processed",
  "org_name": "google.com",
  "org_email": "noreply-dmarc-support@google.com",
  "external_report_id": "4129847120347812934",
  "date_range_begin": "2026-09-29 00:00:00+00",
  "date_range_end": "2026-09-29 23:59:59+00",
  "policy_domain": "acme.com",
  "adkim": "r",
  "aspf": "r",
  "p": "none",
  "sp": "none",
  "pct": 100,
  "fo": null,
  "original_filename": null,
  "error_message": null,
  "envelope_to": "k2v9x4qa7m@dmarc.emailitmail.com",
  "created_at": "2026-09-30 04:12:09.214377+00",
  "updated_at": "2026-09-30 04:12:11.902154+00",
  "processed_at": "2026-09-30 04:12:11.902154+00",
  "records": [
    {
      "object": "dmarc_report_record",
      "source_ip": "198.51.100.24",
      "count": 1840,
      "country_code": "US",
      "country_name": "United States",
      "continent_code": "NA",
      "asn": 64500,
      "as_org": "Example Hosting",
      "latitude": 37.751,
      "longitude": -97.822,
      "disposition": "none",
      "dkim": "pass",
      "spf": "pass",
      "header_from": "acme.com",
      "envelope_from": "emailit.acme.com",
      "envelope_to": null,
      "dkim_domain": "acme.com",
      "dkim_selector": "emailit",
      "dkim_result": "pass",
      "spf_domain": "emailit.acme.com",
      "spf_result": "pass",
      "reason_type": null,
      "reason_comment": null
    },
    {
      "object": "dmarc_report_record",
      "source_ip": "203.0.113.77",
      "count": 12,
      "country_code": "NL",
      "country_name": "Netherlands",
      "continent_code": "EU",
      "asn": 64511,
      "as_org": "Example Networks",
      "latitude": 52.3824,
      "longitude": 4.8995,
      "disposition": "none",
      "dkim": "fail",
      "spf": "fail",
      "header_from": "acme.com",
      "envelope_from": "acme.com",
      "envelope_to": null,
      "dkim_domain": null,
      "dkim_selector": null,
      "dkim_result": null,
      "spf_domain": "acme.com",
      "spf_result": "softfail",
      "reason_type": null,
      "reason_comment": null
    }
  ],
  "meta": { "total": 2, "limit": 100, "offset": 0 }
}
```

**404**

```json
{
  "error": "Report not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/get/

## Upload a report — POST /domains/{id}/dmarc/reports

> Upload a DMARC aggregate (XML, gzip or zip) or forensic (EML) report for a domain. Processing happens in the background.

# Upload a report

Uploads a DMARC report you received elsewhere, for example from a mailbox you used before Emailit. Requires an API key with the `full` scope.

Emailit stores the file and processes it in the background: the response has status `pending`, and the report becomes `processed`, `failed` or `duplicate` (already imported) shortly after. Check it with [Retrieve a report](/docs/api-reference/dmarc/get/). Reports Emailit receives by email need DMARC reports turned on for the domain (Pro plan or higher); uploaded reports are processed either way.

`POST /domains/{id}/dmarc/reports`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Body parameters

Send exactly one of `content_base64` or `content`. The decoded report can be up to 10 MB.

- `content_base64` (string): The file, Base64-encoded. Use it for binary formats: aggregate reports as `.xml`, `.xml.gz` or `.zip`, and forensic reports as `.eml` (AFRF).

- `content` (string): The report as UTF-8 text, for example raw aggregate XML.

- `filename` (string): Original file name, stored as `original_filename`.

## Returns

Returns `202 Accepted` with the new report object. `type` is `null` until processing detects it.

| Status | When |
| --- | --- |
| `400` | Neither `content_base64` nor `content` is set, or the value is empty. |
| `404` | The domain doesn't exist in the workspace. |
| `413` | The decoded report is larger than 10 MB. |

**Request** `POST /domains/{id}/dmarc/reports`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/domains/acme.com/dmarc/reports \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"google-acme.com-2026-09-29.xml.gz\",
    \"content_base64\": \"$(base64 < 'google-acme.com-2026-09-29.xml.gz' | tr -d '\n')\"
  }"
```

**Node.js**

```javascript
import { readFile } from 'node:fs/promises';

const filename = 'google-acme.com-2026-09-29.xml.gz';
const file = await readFile(filename);

const res = await fetch('https://api.emailit.com/v2/domains/acme.com/dmarc/reports', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EMAILIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ filename, content_base64: file.toString('base64') }),
});
const report = await res.json();
```

**Python**

```python
import base64, os, requests

filename = "google-acme.com-2026-09-29.xml.gz"
with open(filename, "rb") as f:
    content = base64.b64encode(f.read()).decode()

r = requests.post(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/reports",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    json={"filename": filename, "content_base64": content},
)
report = r.json()
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);
$filename = 'google-acme.com-2026-09-29.xml.gz';

$response = $client->post('domains/acme.com/dmarc/reports', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'json' => [
        'filename' => $filename,
        'content_base64' => base64_encode(file_get_contents($filename)),
    ],
]);
$report = json_decode($response->getBody(), true);
```

**202**

```json
{
  "object": "dmarc_report",
  "id": "dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
  "type": null,
  "source": "upload",
  "status": "pending",
  "org_name": null,
  "org_email": null,
  "external_report_id": null,
  "date_range_begin": null,
  "date_range_end": null,
  "policy_domain": null,
  "adkim": null,
  "aspf": null,
  "p": null,
  "sp": null,
  "pct": null,
  "fo": null,
  "original_filename": "google-acme.com-2026-09-29.xml.gz",
  "error_message": null,
  "envelope_to": null,
  "created_at": "2026-10-01 12:03:55.607+00",
  "updated_at": "2026-10-01 12:03:55.607+00",
  "processed_at": null
}
```

**400**

```json
{
  "error": "content_base64 or content is required"
}
```

**413**

```json
{
  "error": "Report exceeds maximum size of 10485760 bytes"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/upload/

## List forensic reports — GET /domains/{id}/dmarc/forensic

> List the processed DMARC forensic (failure) reports for a domain: source IP, location, failure type and message details.

# List forensic reports

Returns the processed forensic (RUF) reports for a domain, newest first. Each describes one message that failed DMARC. Headers are left out of the list; [retrieve a forensic report](/docs/api-reference/dmarc/forensic-get/) to see them. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher. Few mailbox providers send forensic reports, and those that do may include personal data from the failed message.

`GET /domains/{id}/dmarc/forensic`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only reports whose period starts on or after this date or date-time.

- `to` (string): Only reports whose period starts on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Reports per page, up to 100.

- `offset` (integer): Number of reports to skip.

The generic `key.condition=value` filters also work on `org_name` and `created_at`, with `match`, `order` and `direction`. See [Filtering](/docs/api-reference/filtering/).

## Returns

Returns `data` and `meta` (`total`, `limit`, `offset`). Each item has:

- `object` (string): `dmarc_forensic_report`.

- `id, report_id` (string): The report ID (`dmr_…`).

- `arrival_date` (string | null): When the failed message reached the receiver.

- `source_ip` (string | null): IP address that sent the message.

- `country_code, country_name, continent_code, asn, as_org, latitude, longitude` (string | integer | number | null): Location and network of the source IP.

- `auth_failure` (string | null): What failed, for example `dmarc`, `spf` or `dkim`.

- `authentication_results` (string | null): The receiver's `Authentication-Results` header.

- `original_mail_from, original_rcpt_to, subject` (string | null): Envelope sender, recipient and subject of the failed message.

- `delivery_result` (string | null): What the receiver did, for example `reject` or `delivered`.

- `reported_domain` (string | null): Your domain as named in the report.

- `created_at` (string | null): When Emailit stored the report.

**Request** `GET /domains/{id}/dmarc/forensic`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/forensic \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d from=2026-09-01
```

**Node.js**

```javascript
const params = new URLSearchParams({ from: '2026-09-01' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/forensic?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: reports } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/forensic",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"from": "2026-09-01"},
)
reports = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/forensic', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['from' => '2026-09-01'],
]);
$reports = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "object": "dmarc_forensic_report",
      "id": "dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
      "report_id": "dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
      "arrival_date": "2026-09-28 17:44:02+00",
      "source_ip": "203.0.113.77",
      "country_code": "NL",
      "country_name": "Netherlands",
      "continent_code": "EU",
      "asn": 64511,
      "as_org": "Example Networks",
      "latitude": 52.3824,
      "longitude": 4.8995,
      "auth_failure": "dmarc",
      "authentication_results": "mx.example.net; dmarc=fail (p=none) header.from=acme.com; spf=softfail smtp.mailfrom=acme.com; dkim=none",
      "original_mail_from": "billing@acme.com",
      "original_rcpt_to": "ada@example.net",
      "subject": "Your invoice is ready",
      "delivery_result": "delivered",
      "reported_domain": "acme.com",
      "created_at": "2026-09-28 18:02:16.448210+00"
    }
  ],
  "meta": { "total": 1, "limit": 25, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/forensic/

## Retrieve a forensic report — GET /domains/{id}/dmarc/forensic/{report_id}

> Retrieve one DMARC forensic report for a domain, including the headers of the message that failed authentication.

# Retrieve a forensic report

Retrieves one forensic (RUF) report with the headers of the message that failed DMARC. Requires an API key with the `full` scope. Forensic reports can contain personal data, such as recipient addresses and subjects.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/forensic/{report_id}`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

- `report_id` (string, required): The report ID (`dmr_…`) of a forensic report.

## Returns

Returns the forensic report with the fields described in [List forensic reports](/docs/api-reference/dmarc/forensic/), plus:

- `headers` (string | null): The original message headers included in the report, as raw text.

Returns `404` if the domain doesn't exist, the report doesn't exist, or it isn't a processed forensic report.

**Request** `GET /domains/{id}/dmarc/forensic/{report_id}`

**cURL**

```bash
curl https://api.emailit.com/v2/domains/acme.com/dmarc/forensic/dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch(
  'https://api.emailit.com/v2/domains/acme.com/dmarc/forensic/dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk',
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const report = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/forensic/dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
report = r.json()
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/forensic/dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$report = json_decode($response->getBody(), true);
```

**200**

```json
{
  "object": "dmarc_forensic_report",
  "id": "dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
  "report_id": "dmr_3MjUXgcEBO0o7FkzeBdlrCY6NYk",
  "arrival_date": "2026-09-28 17:44:02+00",
  "source_ip": "203.0.113.77",
  "country_code": "NL",
  "country_name": "Netherlands",
  "continent_code": "EU",
  "asn": 64511,
  "as_org": "Example Networks",
  "latitude": 52.3824,
  "longitude": 4.8995,
  "auth_failure": "dmarc",
  "authentication_results": "mx.example.net; dmarc=fail (p=none) header.from=acme.com; spf=softfail smtp.mailfrom=acme.com; dkim=none",
  "original_mail_from": "billing@acme.com",
  "original_rcpt_to": "ada@example.net",
  "subject": "Your invoice is ready",
  "delivery_result": "delivered",
  "reported_domain": "acme.com",
  "headers": "From: Acme Billing <billing@acme.com>\r\nTo: ada@example.net\r\nSubject: Your invoice is ready\r\nDate: Mon, 28 Sep 2026 17:43:58 +0000\r\nMessage-ID: <20260928174358.4f1c@mail.acme.com>",
  "created_at": "2026-09-28 18:02:16.448210+00"
}
```

**404**

```json
{
  "error": "Forensic report not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/forensic-get/

## Retrieve statistics — GET /domains/{id}/dmarc/stats

> Get DMARC totals for a domain: volume, pass rate, dispositions, SPF and DKIM results, daily series and top countries and networks.

# Retrieve statistics

Summarizes the aggregate DMARC reports of a domain over a date range: how much mail receivers saw from your domain, how much passed, and where it came from. This is the data behind the Overview tab under **Email API → DMARC reports**. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on (`dmarc_reports: true` in [Update a domain](/docs/api-reference/domains/update/)), which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/stats`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only count report periods that start on or after this date or date-time, for example `2026-09-01`. Without it, all reports are counted.

- `to` (string): Only count report periods that start on or before this date or date-time. A date without a time includes the whole day (UTC).

## Returns

Returns `data` with these fields. A message counts as passing when its DMARC-aligned DKIM or SPF result is `pass`.

- `total_volume` (integer): Messages reported.

- `pass_volume, fail_volume` (integer): Messages that passed and failed.

- `pass_rate` (number): Percentage of messages that passed, rounded to two decimals. `0` when there's no volume.

- `report_count` (integer): Processed reports in the range, aggregate and forensic.

- `dispositions` (object[]): Volume per receiver action: `disposition` (`none`, `quarantine`, `reject`) and `volume`.

- `dkim, spf` (object[]): Volume per aligned result: `result` (`pass` or `fail`) and `volume`.

- `daily` (object[]): One entry per day (`date` as `YYYY-MM-DD`, UTC) with `volume`, `pass_volume` and `fail_volume`.

- `daily_dispositions, daily_dkim, daily_spf` (object[]): The same breakdowns per day: `date`, `disposition` or `result`, and `volume`.

- `top_countries` (object[]): Up to 10 countries by volume: `country_code`, `country_name`, `volume`, `pass_volume`, `fail_volume`.

- `top_asns` (object[]): Up to 10 networks by volume: `asn`, `as_org`, `volume`, `pass_volume`, `fail_volume`.

- `unknown_geo_volume` (integer): Messages from IP addresses that couldn't be located.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/stats`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/stats \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d from=2026-09-28 \
  -d to=2026-09-29
```

**Node.js**

```javascript
const params = new URLSearchParams({ from: '2026-09-28', to: '2026-09-29' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/stats?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: stats } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/stats",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"from": "2026-09-28", "to": "2026-09-29"},
)
stats = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/stats', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['from' => '2026-09-28', 'to' => '2026-09-29'],
]);
$stats = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "total_volume": 3712,
    "pass_volume": 3688,
    "fail_volume": 24,
    "pass_rate": 99.35,
    "report_count": 6,
    "dispositions": [
      { "disposition": "none", "volume": 3712 }
    ],
    "dkim": [
      { "result": "pass", "volume": 3680 },
      { "result": "fail", "volume": 32 }
    ],
    "spf": [
      { "result": "pass", "volume": 3676 },
      { "result": "fail", "volume": 36 }
    ],
    "daily": [
      { "date": "2026-09-28", "volume": 1860, "pass_volume": 1848, "fail_volume": 12 },
      { "date": "2026-09-29", "volume": 1852, "pass_volume": 1840, "fail_volume": 12 }
    ],
    "daily_dispositions": [
      { "date": "2026-09-28", "disposition": "none", "volume": 1860 },
      { "date": "2026-09-29", "disposition": "none", "volume": 1852 }
    ],
    "daily_dkim": [
      { "date": "2026-09-28", "result": "pass", "volume": 1844 },
      { "date": "2026-09-28", "result": "fail", "volume": 16 },
      { "date": "2026-09-29", "result": "pass", "volume": 1836 },
      { "date": "2026-09-29", "result": "fail", "volume": 16 }
    ],
    "daily_spf": [
      { "date": "2026-09-28", "result": "pass", "volume": 1842 },
      { "date": "2026-09-28", "result": "fail", "volume": 18 },
      { "date": "2026-09-29", "result": "pass", "volume": 1834 },
      { "date": "2026-09-29", "result": "fail", "volume": 18 }
    ],
    "top_countries": [
      { "country_code": "US", "country_name": "United States", "volume": 3688, "pass_volume": 3688, "fail_volume": 0 },
      { "country_code": "NL", "country_name": "Netherlands", "volume": 24, "pass_volume": 0, "fail_volume": 24 }
    ],
    "top_asns": [
      { "asn": 64500, "as_org": "Example Hosting", "volume": 3688, "pass_volume": 3688, "fail_volume": 0 },
      { "asn": 64511, "as_org": "Example Networks", "volume": 24, "pass_volume": 0, "fail_volume": 24 }
    ],
    "unknown_geo_volume": 0
  }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/stats/

## List sending sources — GET /domains/{id}/dmarc/sources

> List the IP addresses that sent mail as your domain according to DMARC reports, with volume, pass and fail counts per IP.

# List sending sources

Groups a domain's aggregate DMARC data by source IP address, highest volume first. Use it to spot servers that send as your domain but fail SPF and DKIM. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/sources`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only count report periods that start on or after this date or date-time.

- `to` (string): Only count report periods that start on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Sources per page, up to 200.

- `offset` (integer): Number of sources to skip.

## Returns

Returns `data` and `meta` (`limit`, `offset`). The response has no total; request the next page until `data` has fewer items than `limit`. Each source has:

- `source_ip` (string): The sending IP address.

- `country_code, country_name` (string | null): Where the IP is located.

- `asn, as_org` (integer | string | null): The network the IP belongs to.

- `volume` (integer): Messages reported from this IP.

- `pass_volume, fail_volume` (integer): Messages that passed DMARC-aligned DKIM or SPF, and messages that failed both.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/sources`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/sources \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d from=2026-09-01
```

**Node.js**

```javascript
const params = new URLSearchParams({ from: '2026-09-01' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/sources?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: sources } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/sources",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"from": "2026-09-01"},
)
sources = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/sources', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['from' => '2026-09-01'],
]);
$sources = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "source_ip": "198.51.100.24",
      "country_code": "US",
      "country_name": "United States",
      "asn": 64500,
      "as_org": "Example Hosting",
      "volume": 3688,
      "pass_volume": 3688,
      "fail_volume": 0
    },
    {
      "source_ip": "203.0.113.77",
      "country_code": "NL",
      "country_name": "Netherlands",
      "asn": 64511,
      "as_org": "Example Networks",
      "volume": 24,
      "pass_volume": 0,
      "fail_volume": 24
    }
  ],
  "meta": { "limit": 50, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/sources/

## List countries — GET /domains/{id}/dmarc/countries

> List the countries that mail claiming to be from your domain was sent from, with DMARC volume, pass and fail counts.

# List countries

Groups a domain's aggregate DMARC data by the country of the sending IP, highest volume first. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/countries`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only count report periods that start on or after this date or date-time.

- `to` (string): Only count report periods that start on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Countries per page, up to 200.

- `offset` (integer): Number of countries to skip.

## Returns

Returns `data` and `meta` (`limit`, `offset`). Each country has:

- `country_code` (string | null): ISO 3166-1 alpha-2 code. `null` groups IP addresses that couldn't be located.

- `country_name` (string | null): Country name.

- `continent_code` (string | null): Continent code, for example `EU`.

- `volume` (integer): Messages reported from this country.

- `pass_volume, fail_volume` (integer): Messages that passed DMARC-aligned DKIM or SPF, and messages that failed both.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/countries`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/countries \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d from=2026-09-01
```

**Node.js**

```javascript
const params = new URLSearchParams({ from: '2026-09-01' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/countries?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: countries } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/countries",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"from": "2026-09-01"},
)
countries = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/countries', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['from' => '2026-09-01'],
]);
$countries = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "country_code": "US",
      "country_name": "United States",
      "continent_code": "NA",
      "volume": 3688,
      "pass_volume": 3688,
      "fail_volume": 0
    },
    {
      "country_code": "NL",
      "country_name": "Netherlands",
      "continent_code": "EU",
      "volume": 24,
      "pass_volume": 0,
      "fail_volume": 24
    }
  ],
  "meta": { "limit": 50, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/countries/

## List networks (ASNs) — GET /domains/{id}/dmarc/asns

> List the networks (autonomous systems) that sent mail as your domain, with DMARC volume, pass and fail counts per ASN.

# List networks (ASNs)

Groups a domain's aggregate DMARC data by the autonomous system (ASN) of the sending IP, highest volume first. A network usually maps to a hosting provider or email service, which makes it easier to recognize legitimate senders. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/asns`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only count report periods that start on or after this date or date-time.

- `to` (string): Only count report periods that start on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Networks per page, up to 200.

- `offset` (integer): Number of networks to skip.

## Returns

Returns `data` and `meta` (`limit`, `offset`). Each network has:

- `asn` (integer | null): Autonomous system number. `null` groups IP addresses that couldn't be looked up.

- `as_org` (string | null): Name of the organization that operates the network.

- `volume` (integer): Messages reported from this network.

- `pass_volume, fail_volume` (integer): Messages that passed DMARC-aligned DKIM or SPF, and messages that failed both.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/asns`

**cURL**

```bash
curl -G https://api.emailit.com/v2/domains/acme.com/dmarc/asns \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d from=2026-09-01
```

**Node.js**

```javascript
const params = new URLSearchParams({ from: '2026-09-01' });
const res = await fetch(`https://api.emailit.com/v2/domains/acme.com/dmarc/asns?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: networks } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/asns",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"from": "2026-09-01"},
)
networks = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/asns', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['from' => '2026-09-01'],
]);
$networks = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "asn": 64500,
      "as_org": "Example Hosting",
      "volume": 3688,
      "pass_volume": 3688,
      "fail_volume": 0
    },
    {
      "asn": 64511,
      "as_org": "Example Networks",
      "volume": 24,
      "pass_volume": 0,
      "fail_volume": 24
    }
  ],
  "meta": { "limit": 50, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/asns/

## List reporters — GET /domains/{id}/dmarc/reporters

> List the organizations, such as Google or Microsoft, that sent aggregate DMARC reports for your domain, with report counts.

# List reporters

Lists the organizations that sent processed aggregate reports for a domain, most reports first. Requires an API key with the `full` scope.

Reports arrive only for domains with DMARC reports turned on, which requires the Pro plan or higher.

`GET /domains/{id}/dmarc/reporters`

## Path parameters

- `id` (string, required): The domain ID (`dom_…`) or the domain name.

## Query parameters

- `from` (string): Only count reports whose period starts on or after this date or date-time.

- `to` (string): Only count reports whose period starts on or before this date or date-time. A date without a time includes the whole day (UTC).

- `limit` (integer): Reporters per page, up to 200.

- `offset` (integer): Number of reporters to skip.

## Returns

Returns `data` and `meta` (`limit`, `offset`). Each reporter has:

- `org_name` (string | null): Name of the reporting organization, for example `google.com`. Pass it as `org_name` to [List aggregate reports](/docs/api-reference/dmarc/list/) to see its reports.

- `org_email` (string | null): The reporter's contact address.

- `report_count` (integer): Number of processed aggregate reports from this organization.

Returns `404` if the domain doesn't exist in the workspace.

**Request** `GET /domains/{id}/dmarc/reporters`

**cURL**

```bash
curl https://api.emailit.com/v2/domains/acme.com/dmarc/reporters \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/domains/acme.com/dmarc/reporters', {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: reporters } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/domains/acme.com/dmarc/reporters",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
reporters = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('domains/acme.com/dmarc/reporters', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$reporters = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    { "org_name": "google.com", "org_email": "noreply-dmarc-support@google.com", "report_count": 30 },
    { "org_name": "Enterprise Outlook", "org_email": "dmarcreport@microsoft.com", "report_count": 28 },
    { "org_name": "Yahoo", "org_email": "dmarchelp@yahooinc.com", "report_count": 14 }
  ],
  "meta": { "limit": 50, "offset": 0 }
}
```

**404**

```json
{
  "error": "Domain not found"
}
```

---
Source: https://emailit.com/docs/api-reference/dmarc/reporters/
