---
title: "Analytics · API v1"
description: "Organization-wide and per-domain rollups computed by DMARCguard."
---
# Analytics

Organization-wide and per-domain rollups computed by DMARCguard.

## Organization overview

`GET /dashboard/overview`

The dashboard hero in one call: organization health, one card per domain and the pending action-item counts. The figures may be up to a few minutes old.

- Permission: `analytics:read`
- Key scope: `read`, `full`
- Budget: heavy route, concurrency-capped (https://dmarcguard.io/docs/rate-limits/#heavy-routes)

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/dashboard/overview" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `org_health` | `object` | Organization-wide posture: score, grade and the headline counts. |
| `org_health.overall_score` | `integer` | Composite health score for the organization, 0 to 100. |
| `org_health.grade` | `string` | Letter grade for overall_score: A+, A, A-, B+, B, B-, C+, C, C-, D or F, or N/A before the first report has been received. |
| `org_health.verdict` | `string` | A ready-to-show sentence summarising the organization's posture and how many domains need attention. Display it; its wording is not a value to branch on. |
| `org_health.health_state` | `string` | The coarse band behind the verdict, for colouring: nodata before any report arrives, then secure, warning or critical as the score falls. |
| `org_health.compliance_rate` | `number` | Percentage from 0 to 100 of reported messages that passed DMARC. |
| `org_health.total_messages` | `integer` | Messages reported across every domain. |
| `org_health.total_domains` | `integer` | Domains the organization's reports cover. |
| `org_health.domains_needing_attention` | `integer` | Domains scoring below the healthy band or carrying at least one open action item. |
| `org_health.unique_source_ips` | `integer` | Distinct sending IPs seen across every report. |
| `org_health.has_data` | `boolean` | False until the first report arrives; the scores above mean nothing while it is false. |
| `org_health.forwarded_messages` | `integer` | DMARC-failing messages judged to be ordinary forwarding rather than spoofing. Already inside the failures; it does not change compliance_rate. |
| `domain_cards` | `object[]` | One card per domain, worst score first. |
| `domain_cards[].domain` | `string` | The domain this card is about. |
| `domain_cards[].score` | `integer` | Composite health score for the domain, 0 to 100. |
| `domain_cards[].grade` | `string` | Letter grade for score, on the same scale as org_health.grade. |
| `domain_cards[].verdict` | `string` | A ready-to-show sentence for the card. Display it; its wording is not a value to branch on. |
| `domain_cards[].compliance_rate` | `number` | Percentage from 0 to 100 of this domain's reported messages that passed DMARC. |
| `domain_cards[].total_messages` | `integer` | Messages reported for this domain. |
| `domain_cards[].policy` | `string` | The DMARC policy in force for the domain: none, quarantine or reject. Empty when no policy could be read. |
| `domain_cards[].top_issue` | `string` | Title of the most urgent open action item for this domain; empty when there is none. |
| `domain_cards[].top_issue_severity` | `string` | Priority of top_issue: high, medium or low. Empty when there is no open item. |
| `domain_cards[].action_count` | `integer` | Open action items for this domain. |
| `domain_cards[].inherited` | `boolean` (optional) | True when the policy shown comes from the organizational parent rather than the domain's own record. |
| `domain_cards[].inherited_from` | `string` (optional) | The parent domain the policy was inherited from. |
| `domain_cards[].forwarded_messages` | `integer` | This domain's DMARC-failing messages judged to be ordinary forwarding rather than spoofing. |
| `action_items_summary` | `object` | How many action items are open, bucketed by priority. |
| `action_items_summary.total_pending` | `integer` | Open action items across the organization; the sum of high, medium and low. |
| `action_items_summary.high` | `integer` | Open action items at high priority. |
| `action_items_summary.medium` | `integer` | Open action items at medium priority. |
| `action_items_summary.low` | `integer` | Open action items at low priority. |

### Errors

| Status | When |
| --- | --- |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | Too many expensive requests in flight for this organization; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors

## Message statistics

`GET /statistics`

Totals across every report held: messages, compliant messages, distinct sources and domains.

- Permission: `analytics:read`
- Key scope: `read`, `full`

### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `domain` | `string` | Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization. |

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/statistics?domain=example.com" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `total_reports` | `integer` | Aggregate reports held. |
| `total_messages` | `integer` | Messages those reports account for. |
| `compliant_messages` | `integer` | Of those, how many passed DMARC. |
| `compliance_rate` | `number` | Percentage from 0 to 100 of messages that passed DMARC. |
| `unique_source_ips` | `integer` | Distinct sending IPs seen. |
| `unique_domains` | `integer` | Distinct domains the reports cover. |
| `has_data` | `boolean` | False until the first report arrives. |

### Errors

| Status | When |
| --- | --- |
| `400` | The domain query parameter is not a valid domain name. |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | A transient failure resolving the organization or reading storage; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors

## Compliance score

`GET /compliance-score`

The composite score and letter grade, with the per-domain breakdown behind it. The figures may be up to a few minutes old.

- Permission: `analytics:read`
- Key scope: `read`, `full`

### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `domain` | `string` | Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization. |

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/compliance-score?domain=example.com" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `overall_score` | `number` | Composite health score, 0 to 100. Compliance weighs heaviest, then SPF, DKIM and policy strength equally. |
| `grade` | `string` | Letter grade for overall_score: A+, A, A-, B+, B, B-, C+, C, C-, D or F, or N/A before the first report has been received. |
| `compliance_rate` | `number` | Percentage from 0 to 100 of messages that passed DMARC. |
| `spf_pass_rate` | `number` | Percentage from 0 to 100 of messages with an aligned SPF pass. |
| `dkim_pass_rate` | `number` | Percentage from 0 to 100 of messages with an aligned DKIM pass. |
| `policy_strength` | `number` | A 0 to 100 rating of how far the published policies have moved towards enforcement, averaged across the domains. |
| `domain_scores` | `object[]` | The same figures for each domain behind the overall score. |
| `domain_scores[].domain` | `string` | The domain these figures are for. |
| `domain_scores[].score` | `number` | Composite health score for the domain, 0 to 100. |
| `domain_scores[].grade` | `string` | Letter grade for score, on the same scale as the overall grade. |
| `domain_scores[].compliance_rate` | `number` | Percentage from 0 to 100 of this domain's messages that passed DMARC. |
| `domain_scores[].spf_pass_rate` | `number` | Percentage from 0 to 100 of this domain's messages with an aligned SPF pass. |
| `domain_scores[].dkim_pass_rate` | `number` | Percentage from 0 to 100 of this domain's messages with an aligned DKIM pass. |
| `domain_scores[].policy` | `string` | The DMARC policy the reports saw published for this domain: none, quarantine or reject. |
| `domain_scores[].total_messages` | `integer` | Messages reported for this domain. |
| `total_messages` | `integer` | Messages the score was computed over. |
| `total_reports` | `integer` | Reports the score was computed over. |

### Errors

| Status | When |
| --- | --- |
| `400` | The domain query parameter is not a valid domain name. |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | A transient failure resolving the organization or reading storage; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors

## Compliance score history

`GET /compliance-score/history`

Daily score snapshots for the organization over the requested window.

- Permission: `analytics:read`
- Key scope: `read`, `full`

### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `days` | `integer` | Window length in days, 1 to 365. Out-of-range values fall back to the default. Default 30. |

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/compliance-score/history" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `days` | `integer` | Length of the window in days, as applied. |
| `points` | `object[]` | One snapshot per day, oldest first. |
| `points[].score` | `number` | Composite health score on the snapshot day, 0 to 100. |
| `points[].grade` | `string` | Letter grade for score, on the same scale as the current grade. |
| `points[].compliance_rate` | `number` | Percentage from 0 to 100 of messages that passed DMARC on the snapshot day. |
| `points[].spf_rate` | `number` | Percentage from 0 to 100 of messages with an aligned SPF pass on the snapshot day. |
| `points[].dkim_rate` | `number` | Percentage from 0 to 100 of messages with an aligned DKIM pass on the snapshot day. |
| `points[].total_messages` | `integer` | Messages the snapshot was computed over. |
| `points[].snapshot_date` | `string` (nullable) | Calendar day, YYYY-MM-DD (UTC); null when absent. |

### Errors

| Status | When |
| --- | --- |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | A transient failure resolving the organization or reading storage; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors

## Daily message trends

`GET /trends`

Per-day message, pass and fail counts over the requested window.

- Permission: `analytics:read`
- Key scope: `read`, `full`

### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `domain` | `string` | Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization. |
| `days` | `integer` | Window length in days, 1 to 365. Out-of-range values fall back to the default. Default 30. |

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/trends?domain=example.com" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `days` | `integer` | Length of the window in days, as applied. |
| `points` | `object[]` | One point per day, oldest first. |
| `points[].total_messages` | `integer` | Messages reported for the day. |
| `points[].compliant_messages` | `integer` | Of those, how many passed DMARC. |
| `points[].compliance_rate` | `number` | Percentage from 0 to 100 of the day's messages that passed DMARC. |
| `points[].failed_messages` | `integer` | Of the day's messages, how many failed DMARC. |
| `points[].reports` | `integer` | Reports covering the day. |
| `points[].date` | `string` (nullable) | Calendar day, YYYY-MM-DD (UTC); null when absent. |

### Errors

| Status | When |
| --- | --- |
| `400` | The domain query parameter is not a valid domain name. |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | A transient failure resolving the organization or reading storage; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors

## Top sending IPs

`GET /top-sources`

The highest-volume sending IPs with pass, fail and per-mechanism failure counts.

- Permission: `analytics:read`
- Key scope: `read`, `full`

### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `domain` | `string` | Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization. |
| `limit` | `integer` | Number of IPs to return. Values above the maximum are clamped. Default 10. |
| `offset` | `integer` | Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0. Default 0. |
| `format` | `string` | Download the rows as a CSV attachment instead of JSON. The CSV carries the IP and its total, pass and fail counts only. One of "csv". |

### Example request

```bash
curl "https://api.dmarcguard.io/api/v1/top-sources?domain=example.com" \
  -H "Authorization: Bearer dg_your_key"
```

### Response

| Field | Type | Notes |
| --- | --- | --- |
| `sources` | `object[]` | The requested page, highest volume first. Never null; an empty page is []. |
| `sources[].source_ip` | `string` | The sending IP, exactly as the reports carry it. |
| `sources[].count` | `integer` | Messages this IP sent as your domains. |
| `sources[].pass` | `integer` | Of those, how many passed DMARC. |
| `sources[].fail` | `integer` | Of those, how many failed DMARC. |
| `sources[].spf_fail` | `integer` | Messages whose SPF did not produce an aligned pass. A source can pass DMARC on DKIM alone, so this can be non-zero while fail is 0. |
| `sources[].dkim_fail` | `integer` | Messages whose DKIM did not produce an aligned pass. A source can pass DMARC on SPF alone, so this can be non-zero while fail is 0. |
| `sources[].service_name` | `string` (optional) | The sending service the IP was identified as, when it is recognised. |
| `sources[].service_icon` | `string` (optional) | Icon slug for the identified service. |
| `sources[].service_slug` | `string` (optional) | Stable identifier of the identified service. |
| `total` | `integer` | Sending IPs matching the filter, before paging. |
| `limit` | `integer` | The page size actually applied. |
| `offset` | `integer` | The offset actually applied. |

### Errors

| Status | When |
| --- | --- |
| `400` | The domain query parameter is not a valid domain name. |
| `401` | Missing, malformed, revoked, expired or not-entitled API key. |
| `403` | The key's scope or its owner's role does not allow this operation. |
| `429` | Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After. |
| `500` | Unexpected server error; retry with backoff. |
| `503` | A transient failure resolving the organization or reading storage; retry after a short pause. |

Plus the errors every request can return: https://dmarcguard.io/docs/api/v1/#errors