# Check a custom domain again

> The brands.domains.verify operation: POST /api/v1/brands/{id}/domains/{host}/verify in the REST API and the brands_domains_verify tool in the MCP server.

Asks the domain provider to verify one of the brand's custom domains again, looks up the domain's
`_ownrecord-challenge` TXT record, and records the answer: `verified` once the domain is attached, its DNS points
at Ownrecord and the challenge record carries the domain's token (`ownrecord-verify={token}`); `pending`
otherwise, with the records still needed in `dnsRecords` and what is missing in `check.problems`.

A verified domain that no longer passes, for example because the challenge record was deleted, goes back to
`pending`: its pages stop being served and links stop using it until it is verified again. DNS changes can take a
few hours to be seen; checking again is safe at any time.

## At a glance

- **REST:** `POST /api/v1/brands/{id}/domains/{host}/verify`
- **MCP tool:** `brands_domains_verify`
- **Operation ID:** `brands.domains.verify`
- **Scope:** Organization, or one of its legal entities or brands
- **Permission:** `brands:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `brands:write` scope
- **Since:** M9

## Request

### Path parameters

| Field  | Type       | Description                                  |
| ------ | ---------- | -------------------------------------------- |
| `id`   | `brd_…` ID |                                              |
| `host` | string     | The domain's host name, e.g. `pay.acme.com`. |

### Body

This operation takes no body.

## Response

`200 OK` with a JSON object:

| Field                  | Type                               | Description                                                                                                                                                    |
| ---------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`               | `"brand_domain"`                   |                                                                                                                                                                |
| `host`                 | string                             | The host name, lower case (internationalised names in their xn-- form).                                                                                        |
| `purpose`              | one of `checkout`, `portal`        | `checkout` serves hosted checkout, `portal` the customer portal.                                                                                               |
| `brandId`              | string                             |                                                                                                                                                                |
| `status`               | one of `pending`, `verified`       | `verified` once the host points at Ownrecord and the challenge TXT record proves it's yours; `pending` until then.                                             |
| `verifiedAt`           | date-time or null                  |                                                                                                                                                                |
| `url`                  | string                             | The domain's base URL; hosted pages are served under it once it's verified.                                                                                    |
| `dnsRecords`           | array of object                    | The DNS records to create at the domain's DNS provider: routing, Ownrecord's challenge TXT record, and the hosting provider's TXT record when it asks for one. |
| `dnsRecords[].type`    | one of `A`, `AAAA`, `CNAME`, `TXT` |                                                                                                                                                                |
| `dnsRecords[].name`    | string                             |                                                                                                                                                                |
| `dnsRecords[].label`   | string                             |                                                                                                                                                                |
| `dnsRecords[].value`   | string                             |                                                                                                                                                                |
| `dnsRecords[].purpose` | one of `routing`, `ownership`      |                                                                                                                                                                |
| `check`                | object or null                     |                                                                                                                                                                |

## Examples

### DNS points at Ownrecord and the challenge record is in place

```http
POST /api/v1/brands/brd_01m4tx5nj8emwbyf7y9vcf47x4/domains/pay.acme-app.com/verify
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
```

Response (`200`):

```json
{
  "object": "brand_domain",
  "host": "pay.acme-app.com",
  "purpose": "checkout",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "status": "verified",
  "verifiedAt": "2026-10-20T09:40:00.000Z",
  "url": "https://pay.acme-app.com",
  "dnsRecords": [
    {
      "type": "CNAME",
      "name": "pay.acme-app.com",
      "label": "pay",
      "value": "cname.vercel-dns.com",
      "purpose": "routing"
    },
    {
      "type": "TXT",
      "name": "_ownrecord-challenge.pay.acme-app.com",
      "label": "_ownrecord-challenge.pay",
      "value": "ownrecord-verify=5e0c9a1b7d3f42e8a6b0c4d2e9f17a35",
      "purpose": "ownership"
    }
  ],
  "check": {
    "checkedAt": "2026-10-20T09:40:00.000Z",
    "ownershipVerified": true,
    "dnsConfigured": true,
    "challengeVerified": true,
    "problems": [],
    "simulated": false
  }
}
```

### Still waiting for the DNS records

```http
POST /api/v1/brands/brd_01m4tx5nj8emwbyf7y9vcf47x4/domains/pay.acme-app.com/verify
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
```

Response (`200`):

```json
{
  "object": "brand_domain",
  "host": "pay.acme-app.com",
  "purpose": "checkout",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "status": "pending",
  "verifiedAt": null,
  "url": "https://pay.acme-app.com",
  "dnsRecords": [
    {
      "type": "CNAME",
      "name": "pay.acme-app.com",
      "label": "pay",
      "value": "cname.vercel-dns.com",
      "purpose": "routing"
    },
    {
      "type": "TXT",
      "name": "_ownrecord-challenge.pay.acme-app.com",
      "label": "_ownrecord-challenge.pay",
      "value": "ownrecord-verify=5e0c9a1b7d3f42e8a6b0c4d2e9f17a35",
      "purpose": "ownership"
    }
  ],
  "check": {
    "checkedAt": "2026-10-20T09:00:00.000Z",
    "ownershipVerified": true,
    "dnsConfigured": false,
    "challengeVerified": false,
    "problems": [
      {
        "code": "dns_not_configured",
        "message": "The host doesn't point at Ownrecord yet. Create the DNS records below; changes can take up to a few hours."
      },
      {
        "code": "challenge_missing",
        "message": "Ownrecord can't find its _ownrecord-challenge TXT record yet. Create it with the value below to prove the domain is yours."
      }
    ],
    "simulated": false
  }
}
```

## Errors

| Code                                                                                                             | HTTP status | When                                                                     | What to do                                                                                           |
| ---------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                           | 400         | A field is missing or malformed.                                         | Fix the fields listed in `details.issues` and send the request again.                                |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                         | 404         | The brand isn't visible to the request, or has no domain with this host. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
| [`domain_provider_error`](https://ownrecord.io/docs/reference/errors.md#domain_provider_error)                   | 502         | The domain provider failed; nothing changed.                             | Retry after a short wait. If it keeps failing, contact support with the request ID.                  |
| [`domain_dns_unavailable`](https://ownrecord.io/docs/reference/errors.md#domain_dns_unavailable)                 | 502         | DNS didn't answer for the challenge record; nothing changed.             | Retry after a short wait. Nothing was changed.                                                       |
| [`domain_provider_not_configured`](https://ownrecord.io/docs/reference/errors.md#domain_provider_not_configured) | 500         | Custom domains aren't set up on this deployment.                         | Contact support: the deployment's domain provider credentials are missing.                           |
| [`upstream_timeout`](https://ownrecord.io/docs/reference/errors.md#upstream_timeout)                             | 502         | The domain provider or DNS didn't answer in time; nothing changed.       | Retry the request with the same Idempotency-Key after a short wait.                                  |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)                             | 409         | The brand changed at the same time; retry.                               | Fetch the object again and retry the change.                                                         |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                           | 403         | You can't change this brand's settings.                                  | Ask an organization owner or admin for a role or scope that allows this action.                      |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
