# Custom domains

Every environment receives an address composed by the platform; a custom domain adds a name you own, such as `shop.example.com`. This page explains how to attach a domain, the DNS records to create, how certificates are issued and what happens when a domain stops pointing to Bastide.

## Concepts

A project has two sorts of host names, which the domain list tells apart by their kind (`kind`).

- **auto**: the address the platform composes for each environment on the first deployment that puts it into service. It is served over HTTPS with nothing to do.
- **custom**: a domain of yours, attached to an environment of the project. Production by default, or the environment you choose.

Attaching a domain takes three steps: a **claim** (you declare the name), a **proof** (a DNS record shows it is yours), then **service** (the name enters the route table, and the edge may certify it). Nothing is served or certified before the proof.

A domain is served by only one project at a time, whatever the tenant. Names under `bastide.app` and under the platform's zones are reserved and cannot be attached.

## Records to create

Attaching returns the exact list of records to set at your registrar or DNS host.

| Role | Type | Name | Value |
| --- | --- | --- | --- |
| Proof of ownership | TXT | `_glowsoft-challenge.<domain>` | The returned token, of the form `bastide-…` |
| Traffic for a subdomain | CNAME | `<domain>` | The automatic address of the served environment |
| Traffic for an apex | A and AAAA | `<domain>` | The edge's published addresses, returned in the response |
| Optional: certificate before switching | CNAME | `_acme-challenge.<domain>` | The target returned in `acmeChallenge`, when the platform offers one |

Two routes prove ownership: the TXT record, or a CNAME already pointing to the environment's automatic address. The TXT record does not touch traffic: you can keep your current hosting while you prepare, then switch the CNAME or the A and AAAA records when ready.

The token is single-use and valid for seven days. If no proof arrives in that time, attach the domain again: a fresh token is drawn.

### Apex and www

An apex (`example.com`) cannot carry a CNAME: DNS forbids it at the root of a zone. It is routed with A and AAAA records to the addresses given in the response. A subdomain (`www.example.com`) is routed with a CNAME.

`example.com` and `www.example.com` are two separate domains: attach them one after the other, each with its own proof. To redirect one to the other, declare host-conditioned redirects in the repository's `glowsoft.json` file; see [Routing](routing.md). The `/:path*` pattern does not cover the root `/`, which needs its own rule.

```json
{
  "redirects": [
    {
      "source": "/",
      "has": [{ "type": "host", "value": "www.example.com" }],
      "destination": "https://example.com/",
      "permanent": true
    },
    {
      "source": "/:path*",
      "has": [{ "type": "host", "value": "www.example.com" }],
      "destination": "https://example.com/:path*",
      "permanent": true
    }
  ]
}
```

## Verification

The platform reads DNS by itself: several times in the first minutes, then once an hour until the token expires. It queries the servers that are authoritative for your zone, so as not to depend on a cache still holding the old record. Attaching a pending domain again returns the same token and triggers a read at once.

As long as the proof is missing, the domain's state says what cannot be seen: no record, a TXT record that does not carry the right token, or a CNAME pointing elsewhere.

## TLS certificates

A custom domain's certificate is issued automatically by the edge, over the ACME protocol, after the proof, and renewed the same way. You have no certificate to provide.

The optional `_acme-challenge` record delegates certificate validation to the platform: the certificate can then be issued before traffic arrives, for a switch without downtime. Without it, issuance waits until the domain points to the edge.

## Daily re-verification

A proven domain is read again at least once every 24 hours. It still “points” if one of these holds:

- a CNAME to the environment's automatic address;
- an A or AAAA address among the edge's;
- the proof TXT record, still in place with its token.

If none holds, the domain's row shows **Ne pointe plus** (no longer points) and a notification is sent. Seven days later, if the domain has not reappeared, it is removed as if by a manual removal, and the audit log records it. A read that sees it again clears the loss: a brief outage at your DNS host removes nothing.

## From the console

Open **“Domaines”** (Domains) in the project menu. The table lists the project's addresses, the environment each serves and its state: **Vérifié** (verified), **En attente de preuve** (awaiting proof) or **Preuve introuvable** (proof not found).

1. In the top bar, enter the domain and choose the served environment (production by default).
2. Click **Rattacher** (Attach). The **Preuve à publier** (Proof to publish) panel shows the records to create, with a button to copy each one.
3. Create them at your registrar. The platform reads DNS by itself; the state becomes **Vérifié** once the proof is read.

To remove a domain, open its row's “⋯” menu. A pending claim is removed in one click. A served domain is removed in two steps: the window shows the impact (**Trafic coupé**, traffic cut), then you confirm. Removal is reserved for the Administrator rank.

## Through an agent, the command line or the API

The tools are `domain_add`, `domain_list` and `domain_delete`.

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "domain": "shop.example.com",
  "idempotencyKey": "attach-shop-2026-09-14"
}
```

The `domain_add` response carries `status` (`pending` or `verified`), `verification` (the TXT record to set and its expiry), `routing` (the CNAME target and the A and AAAA addresses) and `acmeChallenge`. `verified` means this project already serves the domain: there is nothing to set.

`domain_list` returns each name with `kind`, `status`, the served environment and `lostSince`, the date since which re-verification no longer sees it pointing.

`domain_delete` on a served domain requires a two-step confirmation: the first call is refused with `confirmation_requise` and carries a nonce and the impact; your MCP client then asks you to confirm, and the second call repeats the nonce within five minutes.

```sh
bastide domain add --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --domain shop.example.com
bastide domain list --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567
bastide domain delete --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --domain shop.example.com
```

Through the API: `POST /v1/projects/{projectId}/domains`, `GET /v1/projects/{projectId}/domains` and `DELETE /v1/projects/{projectId}/domains/{domain}` on `https://api.bastide.app`.

| Action | Minimum rank | Service account |
| --- | --- | --- |
| List | Reader | yes |
| Attach | Maintainer | no |
| Remove | Administrator | no, and human validation is required |

## Limits per tier

| | Discovery | Standard | Pro | Sovereign |
| --- | --- | --- | --- | --- |
| Custom domains per project | 1 | 10 | 100 | 500 |

A fully qualified name is 253 characters at most. A project created by an agent and not yet claimed receives no custom domain.

## Common errors

| Code or state | Cause | Remedy |
| --- | --- | --- |
| `domaine_reserve` | The name is under a platform zone. | Attach a domain you own. |
| `domaine_deja_servi` | Another project already serves this domain. | Remove it from that project before attaching it here. |
| `projet_non_revendique` | The project was created by an agent and is not claimed. | Claim the project in the console, then attach. |
| Pending: no record visible | The TXT record is not yet published, or has not propagated. | Check the exact name `_glowsoft-challenge.<domain>`; wait for the next read. |
| Pending: TXT without the right token | An old token remains, or the value was truncated. | Copy the token returned by the latest attach, without quotes or spaces. |
| Token expired | Seven days without proof. | Attach again: a fresh token is drawn. |
| **Ne pointe plus** | DNS was changed, or the domain left the platform. | Restore the CNAME or the A and AAAA records within seven days, or remove the domain. |
| `confirmation_requise` | Removal of a served domain. | Repeat the call with `confirmationNonce` within five minutes. |

A domain that is proven but listed on a reputation list does not enter the route table; its state says why and whom to contact.

## See also

- [Environments and variables](environments.md)
- [Routing](routing.md)
- [Cache and images](cache.md)
- [Error pages and rate limits](firewall.md)
- [domain.add](../reference/operations/domain.add.md), [domain.delete](../reference/operations/domain.delete.md) (operation pages, in French)
