# Record a decision about one sending source

`PATCH /v1/domain-auth/domains/{domain}/sources/{sourceId}`

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

Records that a sending source is the org's own or one they authorised
(`confirmed`), is not theirs (`rejected`), or withdraws an earlier answer
(`unreviewed`).

**Rejecting does not mute.** A rejected source resolves to `unknown` and
keeps alerting: the customer has said it is not theirs and it is still
sending as their domain, which is the finding rather than its resolution.
The response carries the post-decision `alerts` and `review_state` for
exactly this reason — do not treat a 200 as "cleared".

**Confirming does not always change anything.** A source classified
`sendops` or `sendops_unaligned` cannot be overridden by a decision in
either direction, because SendOps holds the DKIM tokens and the SES
identity configuration. The decision is stored; the class and the
alerting are unchanged, and the underlying fault still needs its record.

`unreviewed` is a WEAKER statement than `rejected`, not a stronger one:
it clears the decision along with its author and timestamp, and the
source returns to undecided.

Reversible in every direction, so it is classified non-destructive and a
test-environment credential may call it. It records a person, so a
client-credentials token is refused.

This endpoint does not publish DNS, change a DMARC policy, or reissue a
reporting address. No endpoint on this API does.

## 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.
- `sourceId` (string<uuid>, required) — The source group's id, from the sources list.

## Request body

Content type: `application/json`

```json
{
  "status": "confirmed",
  "label": "string"
}
```

## Example request

```bash
curl -X PATCH 'https://api.sendops.dev/v1/domain-auth/domains/string/sources/00000000-0000-0000-0000-000000000000' \
  -H "Authorization: Bearer $SENDOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"confirmed","label":"string"}'
```

## Responses

### 200 — The source after the decision. Traffic decoration (`traffic`, `ip_count`, `network_name`, `top_host`) is NOT recomputed here — it costs a ClickHouse scan of the domain's IPs — so those fields come back empty. Re-read the sources list if you need them.

Content type: `application/json`

```json
{
  "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": "string",
      "name": "string",
      "value": "string"
    },
    "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"
  }
}
```

### 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"
  ]
}
```
