> ## Documentation Index
> Fetch the complete documentation index at: https://cactal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect a custom domain

> Learn how to connect a custom domain to a Cactal website: create the domain, add the returned TXT and CNAME or A records at your DNS provider, verify until active, set the primary domain, and confirm the redirect.

This guide takes a domain you own from `pending_dns` to serving your published website with a primary-domain redirect.

## Prerequisites

* An API key with `full_editor` access or higher — see [Create an API key](/docs/create-an-api-key)
* A published website — see the [Quickstart](/docs/quickstart)
* Custom domains enabled for the website by an organization owner in the dashboard (Site settings, Domains); enabling adds the site to the plan and the owner confirms the charge first, see [Plans and limits](/docs/platform/plans-and-limits)
* Access to the domain's DNS records at your registrar or DNS provider

<Steps>
  <Step title="Create the domain">
    `POST /v1/websites/{websiteId}/domains` claims the hostname and returns the DNS records you must create. A website can hold up to 10 custom domains.

    ```bash Add a custom domain theme={null}
    curl -X POST 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/domains' \
      -H "Authorization: Bearer $CACTAL_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{ "hostname": "www.example.com" }'
    ```

    ```json Response (truncated) theme={null}
    {
      "id": "dR7sT9uV1wX3yZ5aB6cD8",
      "kind": "custom",
      "hostname": "www.example.com",
      "status": "pending_dns",
      "isPrimary": false,
      "dnsRecords": [
        {
          "type": "TXT",
          "name": "_cactal.www.example.com",
          "value": "cactal-site-verification=7f3d2c1a-9b8e-4f60-a1d2-5c4b3a291e07",
          "purpose": "ownership"
        },
        {
          "type": "CNAME",
          "name": "www.example.com",
          "value": "custom-domains.cactal.app",
          "purpose": "routing"
        }
      ],
      "failureMessage": null
    }
    ```

    Every domain gets one ownership record plus routing records:

    | Purpose               | Type    | Name                 | Value                              |
    | --------------------- | ------- | -------------------- | ---------------------------------- |
    | Ownership             | `TXT`   | `_cactal.<hostname>` | `cactal-site-verification=<token>` |
    | Routing (subdomain)   | `CNAME` | `<hostname>`         | The target host from `dnsRecords`  |
    | Routing (apex domain) | `A`     | `<hostname>`         | The IP address from `dnsRecords`   |

    Apex domains like `example.com` receive an `A` record instead of a `CNAME`; subdomains like `www.example.com` receive a `CNAME`. Always use the exact values from your response — do not copy values from this page.
  </Step>

  <Step title="Add the DNS records at your provider">
    Create every record in `dnsRecords` at your DNS provider using the returned values. For record names, some providers accept the fully qualified name while others automatically append your domain and want only the host label (for example `_cactal.www`). Check how your provider handles fully qualified names.

    You may proxy the hostname through Cloudflare, Fastly, or another CDN instead of exposing the returned routing value in public DNS. Keep the ownership TXT record unchanged and configure the proxy to forward site requests, including `/_framework/*`, to the Cactal edge.

    DNS changes propagate in minutes at most providers, but TTLs up to 48 hours are possible. You can continue to the next step immediately; verification retries are safe.
  </Step>

  <Step title="Verify until the domain is active">
    `POST /v1/websites/{websiteId}/domains/{domainId}/verify` re-checks ownership, certificate state, and whether HTTPS traffic reaches the matching Cactal domain.

    ```bash Check verification theme={null}
    curl -X POST 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/domains/dR7sT9uV1wX3yZ5aB6cD8/verify' \
      -H "Authorization: Bearer $CACTAL_API_KEY"
    ```

    The domain moves through these statuses:

    | Status                 | Meaning                                                                                                                                            |
    | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `pending_dns`          | Ownership is missing, the hostname is unreachable, or traffic reaches somewhere other than Cactal. `failureMessage` identifies which case applies. |
    | `pending_verification` | DNS looks correct; the edge host and TLS certificate are provisioning.                                                                             |
    | `active`               | Serving traffic with TLS.                                                                                                                          |
    | `failed`               | Verification hit a terminal problem — read `failureMessage`.                                                                                       |
    | `removing`             | Deletion is in progress.                                                                                                                           |

    Cactal also re-checks automatically: every 60 seconds for the first 2 hours, every 5 minutes for the first day, then every 15 minutes. A domain that never verifies is removed after 7 days. Call verify manually whenever you want an immediate answer.

    <Check>
      Repeat verify until the response shows `"status": "active"` and `"failureMessage": null`. Then `https://www.example.com` serves your published website.
    </Check>
  </Step>

  <Step title="Set the primary domain">
    The first custom domain to become active is promoted to primary automatically. To choose a different one:

    ```bash Set primary theme={null}
    curl -X POST 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/domains/dR7sT9uV1wX3yZ5aB6cD8/primary' \
      -H "Authorization: Bearer $CACTAL_API_KEY"
    ```

    Only `active` custom domains can be primary; anything else returns `409`. The response is the domain with `"isPrimary": true`. All other production hosts — the platform subdomain and any other custom domains — redirect to the primary.
  </Step>

  <Step title="Verify the redirect">
    Request the platform subdomain and confirm the redirect to your primary domain:

    ```bash Test the redirect theme={null}
    curl -sI 'https://ember-oak-k7m4p2.cactal.app/' | grep -iE '^(HTTP|location)'
    ```

    <Check>
      You should see `HTTP/2 308` with `location: https://www.example.com/`. Paths and query strings are preserved on the redirect.
    </Check>

    List all domains for the website anytime with `GET /v1/websites/{websiteId}/domains`. Remove one with `DELETE /v1/websites/{websiteId}/domains/{domainId}`; if it was primary, the oldest remaining active custom domain is promoted.
  </Step>
</Steps>

## Troubleshooting

| Symptom                                                                                               | Cause                                                                                                                   | Fix                                                                                                                                                                                      |
| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending_dns` with `Missing TXT record for _cactal.www.example.com`                                   | The ownership record is absent or has the wrong value.                                                                  | Re-create the TXT record exactly as returned, then verify again.                                                                                                                         |
| `pending_dns` with `Domain is unreachable`                                                            | The hostname has no reachable public HTTPS endpoint, or its TLS setup is not ready.                                     | Check the DNS or CDN route and TLS configuration, then verify again.                                                                                                                     |
| `pending_dns` with `Domain is routed elsewhere`                                                       | The hostname answers over HTTPS, but the response does not come from this Cactal domain.                                | Point it to the returned routing target, or update your CDN or reverse proxy to forward `/_framework/*` and site traffic to Cactal.                                                      |
| `pending_dns` with `DNS lookup failed for _cactal...`                                                 | The ownership TXT lookup failed, often during propagation.                                                              | Wait and verify again; retries are scheduled automatically.                                                                                                                              |
| Stuck in `pending_verification`                                                                       | Certificate issuance is still in progress.                                                                              | Allow a few minutes; keep the DNS records in place and verify again.                                                                                                                     |
| `failed`                                                                                              | A terminal edge or certificate problem; `failureMessage` has details, and verification expires after 7 days of failure. | Fix the recorded cause and verify, or delete and re-create the domain.                                                                                                                   |
| `403` `Custom domains are not enabled for this website` (`blocked_reason: "custom_domains_disabled"`) | Custom domains are disabled for the website, so nothing can be attached. Publishing on the Cactal domain stays free.    | Ask an organization owner to enable custom domains for the website in Site settings, where they confirm the site charge, then retry. See [Plans and limits](/docs/platform/plans-and-limits). |
| `409` `This custom domain is already claimed`                                                         | The hostname is attached to another website.                                                                            | Remove it there first, or use a different hostname.                                                                                                                                      |
| `409` `Custom domain quota reached`                                                                   | The website already has 10 custom domains.                                                                              | Delete an unused domain.                                                                                                                                                                 |
| `400` `Invalid custom domain hostname`                                                                | The value includes a scheme, path, wildcard, IP address, or a reserved Cactal host.                                     | Send a bare lowercase hostname such as `www.example.com`.                                                                                                                                |

## Next steps

* [Domains concepts](/docs/concepts/domains) for platform subdomains, preview hosts, and SSL
* [Preview drafts and versions](/docs/guides/preview-drafts-and-versions) to review changes before they reach your domain
