# List a domain's grouped sending sources

`GET /v1/domain-auth/domains/{domain}/sources`

- Authentication: required (Bearer token)
- Required scope: `api.domain_auth.view`

Returns the sources sending as this domain, busiest first, each with its
classification, confidence, review state and any open finding — including
the literal DNS record that clears the finding where one exists.

**The five effective classes are not five words for "suspicious", and
conflating them is the most damaging mistake available on this surface.**
`sendops` is the org's own SES. `sendops_unaligned` is *also* their own
SES, failing alignment — nearly always a missing custom MAIL FROM or a
DKIM token rotated without republishing the CNAMEs, i.e. a configuration
fault to fix rather than an intruder. `sendops_relayed` is their own mail
arriving via a host that is not their SES, which is what a forwarder or a
mail-security scanner looks like. `known` is a third party they have
confirmed. Only `unknown` is a genuine stranger, and most of those are a
third party nobody has registered yet.

`review_state` says why a row is outstanding and therefore what would end
it. `awaiting_decision` is the only set a decision can clear;
`open_finding` rows are already decided or were never the customer's to
decide, and no decision will move them.

`traffic_partial` means the pass rates were computed from a capped scan
of this domain's sending IPs, so a source reporting no traffic may still
have some. Do not read an absent `traffic` as "sent nothing" while it is
true.

## Path parameters

- `domain` (string, required) — The domain, by NAME (`acme.com`). A UUID is also accepted. Matched case-insensitively, and scoped to the calling org — a domain belonging to another org is a 404, never a 403.

## Query parameters

- `limit` (integer, optional) — Page size (1–200). Default 50.
- `cursor` (string, optional) — Opaque cursor returned from the previous page.
- `class` (string enum, optional) — Narrow to one effective class.
- `review_state` (string enum, optional) — Narrow to what is outstanding and why. Applied after the page is read, because `review_state` is derived rather than stored — so a filtered page may hold fewer rows than `limit` without meaning there are no more.

## Example request

```bash
curl 'https://api.sendops.dev/v1/domain-auth/domains/string/sources' \
  -H "Authorization: Bearer $SENDOPS_API_KEY"
```

## Responses

### 200 — Cursor-paginated list of grouped sending sources

Content type: `application/json`

```json
{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "display_name": "string",
      "group_kind": "provider",
      "group_key": "string",
      "class": "sendops",
      "effective_class": "sendops",
      "confidence": "certain",
      "status": "unreviewed",
      "review_state": "none",
      "alerts": true,
      "decided_at": "2026-05-17T20:00:00Z",
      "first_seen_at": "2026-05-17T20:00:00Z",
      "last_seen_at": "2026-05-17T20:00:00Z",
      "volume_30d": 0,
      "ip_count": 0,
      "network_name": "string",
      "top_host": "string",
      "top_host_others": 0,
      "traffic": {
        "volume": 0,
        "pass_volume": 0,
        "pass_rate": 0,
        "adjusted_pass_rate": 0,
        "authenticable_volume": 0,
        "campaign_excluded": true,
        "last_received_at": "2026-05-17T20:00:00Z"
      },
      "diagnosis": {
        "code": "dkim_missing_cname",
        "vendor": "string",
        "record": {
          "type": null,
          "name": null,
          "value": null
        },
        "diagnosed_at": "2026-05-17T20:00:00Z"
      },
      "annotations": [
        {
          "kind": "identity",
          "title": "string",
          "body": "string",
          "doc_url": "string",
          "confidence": "confirmed"
        }
      ],
      "shared_pool": {
        "vendor": "string",
        "doc_url": "string"
      }
    }
  ],
  "traffic_partial": true,
  "pagination": {
    "has_more": true,
    "next_cursor": "string"
  }
}
```

### 401 — Missing, malformed, or unknown API key

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 403 — Key lacks the required scope or plan limit violated

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 404 — Resource not found

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 422 — Query parameter or path value failed validation

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 429 — Per-org rate limit exceeded

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 500 — Unexpected server-side failure. The `code` is `internal_error`. The
`request_id` field can be quoted to SendOps support to investigate.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```
