# Add a receiving domain

`POST /v1/inbound/domains`

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

Records a receiving subdomain and creates its SES identity in the
receiving region.

**It is not receiving mail yet, and the `status: pending_dns` in the
response is the whole reason.** SendOps holds no DNS credentials and
writes no DNS anywhere, so publishing the records in `records` is yours
to do. A caller that treats this 201 as "inbound is live" will report
success and then wonder where the mail went. Call
`POST /v1/inbound/domains/{domain}/verify` once the records are up.

The domain must be equal to, or a subdomain of, a domain your org
already holds in SendOps — otherwise 422 `receiving_domain_not_owned`.
Some stack on the AWS account must carry the inbound module, or 422
`inbound_not_deployed`. A domain already recorded on the account is 409.

The SES identity goes in the RECEIVING region, which is the region of
the stack carrying the inbound module and need not be your sending
region: only 22 AWS regions receive mail at all, so `cross_region: true`
is ordinary rather than a misconfiguration.

## Request body

Content type: `application/json`

```json
{
  "domain": "string"
}
```

## Example request

```bash
curl -X POST 'https://api.sendops.dev/v1/inbound/domains' \
  -H "Authorization: Bearer $SENDOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"string"}'
```

## Responses

### 201 — The receiving domain, with the DNS records to publish

Content type: `application/json`

```json
{
  "domain": "string",
  "id": "00000000-0000-0000-0000-000000000000",
  "status": "pending_dns",
  "last_error": "string",
  "region": "string",
  "cross_region": true,
  "mx_verified_at": "2026-05-17T20:00:00Z",
  "identity_verified_at": "2026-05-17T20:00:00Z",
  "records": [
    {
      "type": "MX",
      "name": "string",
      "value": "string",
      "verified": true
    }
  ],
  "webhook": {
    "url": "https://example.com",
    "allow_patterns": [
      "string"
    ],
    "spam_posture": "tag",
    "virus_posture": "tag",
    "status": "unset",
    "error": "string",
    "published_at": "2026-05-17T20:00:00Z"
  },
  "created_at": "2026-05-17T20:00:00Z"
}
```

### 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 — Either the credential lacks the required scope (`code: invalid_scope`), or it is bound to the `test` environment and this operation is irreversible (`code: test_environment_forbidden`). Branch on `code`: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description.

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

### 409 — The mutation is rejected by a state rule rather than a bad request. The
`code` is `conflict`.

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 — A query parameter, path value or body field failed validation.

The body is a `validation_failed` Problem. When the refusal is about a
reference — a segment key, template slug, topic or attribute name the
organization does not have — it additionally carries `validation_code`,
`field`, `line`/`column`, `missing`, `candidates` and `next_step`, so a
client can correct the call without a second round of guessing. See
`ValidationProblem`.

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"
  ],
  "validation_code": "invalid_syntax",
  "field": "string",
  "line": 0,
  "column": 0,
  "missing": [
    {
      "kind": "segment",
      "key": "string"
    }
  ],
  "candidates": {},
  "next_step": "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"
  ]
}
```
