# POST /v1/inbound/domains

Claim a domain for an inbound stream

Claims the domain for one of the server’s inbound streams and answers the two DNS records to add: an MX that brings the domain’s mail to Sendora and a TXT record that proves the claim. Mail to any address on the domain is accepted once both records are seen, which Sendora checks on its own and on request. A stream holds one domain; the first account to verify a name holds it.

Takes a server key (`sk_…`, or `sk_test_…` on a test server) as a bearer token. TypeScript: `sendora.inboundDomains.create({ streamId, domain })`. Python: `sendora.inbound_domains.create(stream_id=..., domain=...)`.

## Request body

- `streamId` (string (uuid), required): An inbound stream of the server.
- `domain` (string, required): The domain mail is sent from, such as example.se; lower-cased and IDNA-encoded.

Example:

```json
{
  "streamId": "b7d2e4f6-1a3c-4d5e-9f80-2c4e6a8b0d1f",
  "domain": "post.example.se"
}
```

## Responses

### 201 A domain on an inbound stream and the two records it needs.

- `inboundDomainId` (string (uuid), required)
- `streamId` (string (uuid), required): The inbound stream the domain delivers to.
- `domain` (string, required): The domain, lower-cased and IDNA-encoded.
- `verified` (boolean, required): True while both records are seen; only then is mail to the domain accepted.
- `createdAt` (string (date-time), required)
- `lastCheckedAt` (string (date-time) or null, required): When the records were last looked up.
- `unverifiedAt` (string (date-time) or null, required): When a verified domain lost its records. Mail to it is deferred for 72 hours from then and refused after, until the records are back.
- `mx` (object, required): The MX record that brings the domain’s mail to Sendora, at any priority.
  - `type` ("MX", required)
  - `host` (string, required): The name to create the record under.
  - `value` (string, required): The value the record must hold.
  - `verified` (boolean, required)
  - `verifiedAt` (string (date-time) or null, required)
- `txt` (object, required): The TXT record that proves the claim; it carries a value only you were shown.
  - `type` ("TXT", required)
  - `host` (string, required): The name to create the record under.
  - `value` (string, required): The value the record must hold.
  - `verified` (boolean, required)
  - `verifiedAt` (string (date-time) or null, required)

Example:

```json
{
  "inboundDomainId": "6c2a9e1d-3f4b-4a8c-9d0e-1b2c3d4e5f60",
  "streamId": "b7d2e4f6-1a3c-4d5e-9f80-2c4e6a8b0d1f",
  "domain": "post.example.se",
  "verified": false,
  "createdAt": "2026-09-19T09:00:00.000Z",
  "lastCheckedAt": null,
  "unverifiedAt": null,
  "mx": {
    "type": "MX",
    "host": "post.example.se",
    "value": "10 inbound.sendora.se.",
    "verified": false,
    "verifiedAt": null
  },
  "txt": {
    "type": "TXT",
    "host": "_sendora-inbound.post.example.se",
    "value": "sendora-inbound=7f3a9c2e5b1d4f6a8c0e2b4d6f8a1c3e",
    "verified": false,
    "verifiedAt": null
  }
}
```

## Errors

Every error answers `error`, the code, and `message`, a sentence for a person. A code that adds fields is shown in full below the table.

| Code | Status | Meaning |
| --- | --- | --- |
| `invalid_request` | 400 | The body, the query or a header does not match what the route takes. |
| `domain_reserved` | 400 | The name is Sendora’s own, or a return-path host; no account can receive on it. |
| `unauthorized` | 401 | The key is missing, malformed or revoked. |
| `wrong_token_kind` | 403 | The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes. |
| `inbound_domain_exists` | 409 | The stream already has a domain, or another account holds the name verified. |
| `stream_not_found` | 422 | The streamId names no stream of this server. |
| `stream_archived` | 422 | The stream is archived and takes no new messages. |

### `invalid_request`

- `issues` (array of object): One entry per invalid field; absent when a header is wrong.
  - `path` (string, required): The field, dotted, such as to.0.email; empty when the whole body is wrong.
  - `message` (string, required)

Example:

```json
{
  "error": "invalid_request",
  "message": "The request is invalid: to.0: Invalid email address",
  "issues": [
    {
      "path": "to.0",
      "message": "Invalid email address"
    }
  ]
}
```

### `inbound_domain_exists`

- `inboundDomainId` (string (uuid) or null, required): The domain the stream already has, when that is the reason; null when another account holds the name verified.

Example:

```json
{
  "error": "inbound_domain_exists",
  "message": "The stream already has a domain; remove it first.",
  "inboundDomainId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
```
