> ## 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.

# Domains

> How websites get hostnames: the automatic platform subdomain, the draft preview host, and custom domains — DNS records, verification, statuses, the primary domain redirect, and SSL.

Every published website serves from an automatic platform subdomain for free. A site counts toward your plan when an organization owner enables custom domains for it; sites on Cactal domains are unlimited and free. A website with custom domains enabled counts once toward your plan and can hold up to 10 of them, with platform-managed SSL.

## Platform subdomain

Each website gets one hostname of the form `<website-slug>.cactal.app` at creation. A prompt-created website starts with a temporary address; its first automatic naming step aligns the hostname with the generated website slug. Cactal uses the readable slug when it is globally available and adds a short random suffix, such as `<website-slug>-k7m4p2.cactal.app`, when needed for uniqueness. After that initial step, the assigned hostname stays stable if the website name or slug is renamed. It is `active` immediately, requires no DNS work, and serves the published version once you publish.

`GET /v1/websites/{websiteId}/domains` lists all of a website's domains, platform subdomain included.

### Choosing a different address

`POST /v1/websites/{websiteId}/domains/platform/rename` moves the website to a label you pick:

```bash theme={null}
curl -X POST "https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/domains/platform/rename" \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "acme-studio"}'
```

The label is the part before `.cactal.app`: 1 to 40 characters, lowercase letters, digits, and inner hyphens. A handful of labels are reserved by Cactal. If another website already holds the label, the request fails with `409`, so pick another and retry.

<Warning>
  The old hostname is released the moment the rename succeeds. There is no redirect and no grace period: links to `<old-label>.cactal.app` stop working, the draft and version preview hosts move with it, and another website can claim the label. Connect a custom domain before sharing a site widely if you want an address you control.
</Warning>

The response carries `previousHostname` alongside the new record, so you can tell exactly what was released. Custom domains are untouched by a rename, and a primary custom domain stays the site's canonical address.

## Preview hosts

Prefixes on the platform label address other versions of the site:

| Host                        | Serves                                                     |
| --------------------------- | ---------------------------------------------------------- |
| `<label>.cactal.app`        | The published version                                      |
| `draft--<label>.cactal.app` | The head version — your current draft                      |
| `v<n>--<label>.cactal.app`  | Reserved for pinned-version previews; responds `501` today |

The draft host is excluded from search: every response carries `X-Robots-Tag: noindex, nofollow`, and its `robots.txt` disallows all crawling. Draft URLs are shareable with anyone, but they are not access-controlled — treat draft content as public. See [Preview drafts and versions](/docs/guides/preview-drafts-and-versions).

## Custom domains

A custom domain connects your own hostname — `www.acme.com` or the apex `acme.com` — to a website. Domains can only be attached while custom domains are enabled for the website, an owner-only dashboard setting (Site settings, Domains). Enabling is the billing moment: it adds the site to the plan, and the owner confirms the charge in the enable dialog first. After that, adding or removing domains never changes billing, and additional domains on the same website cost nothing extra. Attempts to add a domain while enablement is off return `403` with `blocked_reason: custom_domains_disabled`. The lifecycle is create, set DNS, verify, go active.

<Steps>
  <Step title="Create the domain">
    ```bash 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.acme.com"}'
    ```

    The response is the domain in status `pending_dns`, including the exact `dnsRecords` to create.
  </Step>

  <Step title="Create the DNS records">
    Each entry in `dnsRecords` has a `type`, `name`, `value`, and `purpose`. There are always two purposes:

    | Purpose     | Record                                 | Example                                    |
    | ----------- | -------------------------------------- | ------------------------------------------ |
    | `ownership` | `TXT` at `_cactal.<hostname>`          | value `cactal-site-verification=<token>`   |
    | `routing`   | `CNAME` at `<hostname>` for subdomains | value `custom-domains.cactal.app`          |
    | `routing`   | `A` at `<hostname>` for apex domains   | value is the platform IP from the response |

    ```json theme={null}
    "dnsRecords": [
      { "type": "TXT", "name": "_cactal.www.acme.com", "value": "cactal-site-verification=6f1c...", "purpose": "ownership" },
      { "type": "CNAME", "name": "www.acme.com", "value": "custom-domains.cactal.app", "purpose": "routing" }
    ]
    ```

    <Warning>
      Copy the ownership record exactly. The routing record is the direct setup, but you may instead proxy the hostname through a CDN or reverse proxy that forwards requests to the Cactal edge. Verification confirms that HTTPS traffic reaches the matching Cactal domain, so a proxied DNS answer does not block activation.
    </Warning>
  </Step>

  <Step title="Verify">
    `POST /v1/websites/{websiteId}/domains/{domainId}/verify` re-checks immediately and returns the updated domain. Cactal also re-checks automatically: every 60 seconds for the first 2 hours, every 5 minutes until 24 hours, then every 15 minutes.
  </Step>

  <Step title="Active">
    Once DNS resolves and the edge finishes SSL issuance, the status becomes `active` and the domain serves the published site over HTTPS. Certificates are provisioned and renewed by the platform — you never handle them.
  </Step>
</Steps>

<Note>
  An unverified domain expires: if it has not reached `active` within 7 days of creation, the claim is removed and you must add the domain again. Claims are also exclusive — a hostname already claimed by any website returns `409` `This custom domain is already claimed`.
</Note>

## Domain statuses

| Status                 | Meaning                                                                                                                                                              |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending_dns`          | Waiting for the ownership TXT or for HTTPS traffic to reach Cactal; `failureMessage` distinguishes missing ownership, an unreachable hostname, and routing elsewhere |
| `pending_verification` | DNS resolves; the edge is completing routing and SSL issuance                                                                                                        |
| `active`               | Serving the published website over HTTPS                                                                                                                             |
| `failed`               | Verification or SSL failed; read `failureMessage`, fix, and verify again                                                                                             |
| `removing`             | Deletion in progress; the platform retries edge cleanup until it completes                                                                                           |

## Primary domain

One `active` custom domain can be primary — the canonical hostname. The first custom domain to activate is promoted automatically; change it with `POST /v1/websites/{websiteId}/domains/{domainId}/primary`. Setting a non-active domain primary returns `409` `Only active custom domains can be primary`.

While a primary exists, every other hostname for the website — the platform subdomain and any non-primary custom domains — answers with an HTTP `308` redirect to the primary, preserving path and query. Removing the primary promotes the oldest remaining `active` custom domain, if any.

`DELETE /v1/websites/{websiteId}/domains/{domainId}` removes a custom domain. Removing a domain, even the final one, never changes the website's charge; only disabling custom domains does, which disconnects every domain and credits unused time on the next invoice. Keep your DNS records in place until the removal completes, then delete them at your DNS provider.

Moving a website to the trash disables custom domains automatically, so its charge stops and each hostname becomes claimable by another website. Restoring the website returns it with custom domains disabled: an owner re-enables them, then you add and verify domains again, with the new DNS records the fresh claim returns. The platform subdomain is never removed. See [Websites](/docs/concepts/websites).

## Constraints

| Constraint                 | Value                                                                                 |
| -------------------------- | ------------------------------------------------------------------------------------- |
| Custom domains per website | 10                                                                                    |
| Hostname length            | maximum 253 characters, ASCII, lowercase                                              |
| Hostname shape             | At least two DNS labels; no IP addresses, wildcards, paths, or `cactal.app` hostnames |
| Verification window        | 7 days from creation before the claim expires                                         |
| Platform subdomain         | One per website; cannot be changed or removed                                         |
| Serving                    | Custom domains serve only once the website is published                               |

## Next steps

<Columns cols={2}>
  <Card title="Connect a custom domain" icon="plug" href="/docs/guides/connect-a-custom-domain">
    Registrar-level DNS walkthrough with troubleshooting.
  </Card>

  <Card title="Publishing" icon="rocket" href="/docs/concepts/publishing">
    Publish the website your domains point at.
  </Card>
</Columns>
