# Deliverability Score

Every verified sending domain gets a **Deliverability Score**: a number from 0 to 100, a band, the named checks behind it grouped into five pillars, and one task for each thing that needs fixing. Each task says **where** the fix is done: in SendOps, in your DNS, in your AWS account, or with a third party. DNS tasks carry the exact records to publish.

Two read-only endpoints expose it. Both need `api.reports.view` and work on every plan.

| Endpoint | Returns |
|---|---|
| `GET /v1/deliverability` | One row per verified domain, plus `org_summary`: the organization's **worst** domain. |
| `GET /v1/deliverability/{domain}` | One domain in full: pillars, every check, the ranked tasks and the latest receiver-side check. |

Full schemas are in the sidebar under **Deliverability**. This is a different thing from `GET /v1/reports/deliverability`, which is a time series of sent, delivered, bounced and complained counts.


  Running the authentication check, re-checking a domain and marking a task handled are dashboard actions. No endpoint here changes anything.


## What a domain returns

- **`score`**: 0–100, the weighted share of the checks that could be decided which passed. Checks that could not be decided (`unknown`) are **left out of the calculation**, not counted as zero or as a pass.
- **`band`**: `healthy` (90 and above), `at_risk` (70–89) or `failing` (below 70). **Any `blocking` task forces `failing`**, whatever the number. `unassessed` means no weighted check could be decided, and `score` is then null. `band_label` is the display text ("Healthy", "At risk", "Failing").
- **`coverage`**: how many weighted checks were `scored` out of `total`, how many were `unknown`, and how many were `not_applicable`. Always read the score together with its coverage. `low_confidence` is true when fewer than three checks were scored.
- **`pillars`**: authentication (40), reputation (25), compliance (15), hygiene (10) and infrastructure (10). Each has a `label`, its nominal `weight`, its own `score` (null when none of its checks was decided), its `coverage`, and `not_applicable` when every check in it is out of scope, for example compliance when you send below every mailbox provider's volume threshold. When the score and coverage don't tell the whole story, a pillar also carries a **`summary`**: one plain sentence saying why it doesn't apply ("You send about 4 a day to Outlook; these rules apply only above 5,000 a day to Outlook, Gmail or Yahoo.") or what its unmeasured checks need and when they will be measured. `summary` is left out when there is nothing to add.
- **`checks`**: every named check, with `status` (`pass`, `warn`, `fail`, `unknown` or `not_applicable`), what was `measured` beside the `threshold` it is judged against, the `basis` of the measurement, and the `source` it was read from and when. Compliance checks carry the mailbox provider's published `rule` (Microsoft, Google or Yahoo) with its source URL and dates. A `warn` counts as a pass and carries an advisory task. An `unknown` check's `basis` says what it needs and, where SendOps can know it, when it will be measured.
- **`tasks`**: ranked by severity, then by the weight of the check that owns them. See below.
- **`latest_probe`**: what SendOps's own receiver saw the last time it received a message from this domain through your SES: SPF, DKIM and DMARC results and every DKIM signature. It confirms the signature; it does **not** tell you which folder a mailbox provider would put the message in. Null if the domain has never been checked this way.

A check SendOps has no way to measure yet is **left out entirely**, not reported as `unknown`: it has no row in `checks` and no part in the score or coverage, and it appears on its own once it can be measured. Today that applies to the blocklist check and the one-click unsubscribe check (and the provider rules that rely on it). Don't treat the list of check keys as fixed.

Every failing or warning check points at exactly one task: its own `task`, or `covered_by`, the key of a task owned by another check that fixes the same root cause. An `unknown` check carries the task that would measure it.

### Tasks: read `where` first

| `where` | Meaning |
|---|---|
| `sendops` | A change inside SendOps. `link` is the app path. |
| `dns` | Publish the literal `records` (`type`, `name`, `value`) at your DNS host. |
| `aws` | A change in your AWS account. `link` is the AWS console URL. |
| `third_party` | Done outside SendOps, AWS and your DNS. |

`then` names a second place when a fix spans two, such as verifying a domain in AWS and then publishing its DKIM records in DNS. `severity` is `blocking` (mail is being lost now), `important` or `advisory`.

A task with `verifies: true` closes by itself at the next check once fixed. One with `verifies: false` cannot be confirmed by SendOps and can only be marked handled in the dashboard, which hides it for 90 days; `acknowledged_until` is set while that is the case. Task keys are stable, so they are safe to store.

### Before the first score

A newly verified domain has no score until its first nightly run. The list shows it with `status: not_yet_assessed` and null `score`, `coverage` and `taken_at`. `GET /v1/deliverability/{domain}` answers **`202`** with `status: not_yet_assessed` and `next_nightly_at`. Neither ever returns zeros in place of a score.

## The organization summary

`org_summary` describes the organization as its **worst** assessed domain (worst band, then lowest score), never an average. It covers every domain, not just the current page, and also counts open and blocking tasks and how many domains are assessed or still waiting. `worst_band` is `unassessed` and `worst_domain` null until some domain has a score.

The list is cursor-paginated over the domains; see [Pagination](/api-reference/pagination). Task counts leave out tasks marked handled.

## When it updates, and how often to poll

Scores are recalculated **nightly at 05:40 UTC**, and again when someone re-checks a domain in the dashboard. `taken_at` says when the domain's current score was taken, and `next_nightly_at` when the next nightly run is due.

**Don't poll more than once an hour.** Between runs nothing changes. A sensible pattern is to read once shortly after 05:40 UTC, and again after you know someone has re-checked a domain.

## Example

```bash
curl https://api.sendops.dev/v1/deliverability/example.com \
  -H "Authorization: Bearer $SENDOPS_API_KEY"
```

`{domain}` is the domain name, matched case-insensitively; the domain's id works too. A domain your organization does not own is a `404`. An abridged response:

```json
{
  "status": "assessed",
  "domain": "example.com",
  "taken_at": "2026-10-12T05:40:00Z",
  "score": 45,
  "band": "failing",
  "band_label": "Failing",
  "low_confidence": false,
  "coverage": { "scored": 18, "total": 20, "unknown": 2, "not_applicable": 12 },
  "pillars": [
    {
      "pillar": "compliance",
      "label": "Provider requirements",
      "weight": 15,
      "score": null,
      "not_applicable": true,
      "coverage": { "scored": 0, "total": 0, "unknown": 0, "not_applicable": 11 },
      "summary": "You send about 4 a day to Outlook; these rules apply only above 5,000 a day to Outlook, Gmail or Yahoo."
    }
  ],
  "checks": [
    {
      "key": "ses_dkim_aligned",
      "pillar": "authentication",
      "status": "fail",
      "measured": "signed d=amazonses.com",
      "threshold": "mail signed d=example.com",
      "task": { "key": "ses_dkim_aligned.signed_as_ses" },
      "covered_by": null
    }
  ],
  "tasks": [
    {
      "key": "ses_dkim_aligned.signed_as_ses",
      "check_key": "ses_dkim_aligned",
      "title": "Mail from example.com is signed as amazonses.com, not as example.com",
      "where": "aws",
      "where_label": "In your AWS account",
      "then": "dns",
      "link": "https://us-east-1.console.aws.amazon.com/ses/home?region=us-east-1#/identities",
      "records": [
        { "type": "CNAME", "name": "a1._domainkey.example.com", "value": "a1.dkim.amazonses.com" }
      ],
      "verifies": true,
      "severity": "blocking",
      "acknowledged_until": null
    }
  ],
  "latest_probe": null,
  "next_nightly_at": "2026-10-13T05:40:00Z"
}
```

## Errors

| Status | When |
|---|---|
| `202` | The domain has no score yet (`not_yet_assessed`). |
| `403` | The credential lacks `api.reports.view`. |
| `404` | The domain is not one of your organization's verified domains. |
| `422` | On the list, a `cursor` that is invalid or points at a domain that is no longer verified. Start again without it. |

## Related

- [Deliverability score](https://help.sendops.dev/reports/deliverability-score): what each check and task means, for the person fixing it.
- [MCP Server](/api-reference/mcp#sending-readiness-and-deliverability): the same score for agents, as the `deliverability` section of `sending_readiness_check`.
- [Domain Auth (DMARC)](/api-reference/domain-auth): the DMARC reports and policy ladder behind several authentication checks.