# Addresses & custom domains

> Every instance has a Lunary address you can rename; bring your own domain to serve it on hostnames you own.

Every routed instance answers at a **Lunary address**, `<label>.<org>.lunary.cloud`, where the label defaults to the instance slug. You can rename that label, and you can serve the instance on hostnames under a **domain you own**: prove the domain once with a TXT record, then map as many hostnames as you like (a CNAME each, or A records for the domain itself). All of it is in the dashboard, the API, and MCP, so an agent can do the whole setup.

## Renaming the address

Dashboard: the instance's **Settings → Address** card. API: `PUT /api/v1/instances/{slug}/address` with `{ "address": "workflows.acme.lunary.cloud" }` (`write_instances`). MCP: `set_instance_address`.

- The new address is one label under your zone: lowercase letters, digits, hyphens.
- The app restarts on the new name, and its own URL settings follow (n8n's host, Twenty's server URL, Docmost's app URL, Ghost's site URL). The response is `202` with the apply run in `meta.run_id`.
- The old name stops answering. Custom hostnames pointing at it keep working.
- Only self-serve instances (deployed from the dashboard or `POST /instances`) can be renamed; instances we manage declaratively for you return `422 not_operator_managed` or `422 not_renamable`.

## Bringing your own domain

Two steps, in this order.

### 1. Verify the domain

Dashboard: the **Domains** page. API: `POST /api/v1/domains` with `{ "name": "example.com" }` (`write_instances`). MCP: `add_domain`.

The response carries the TXT record to publish at your DNS host:

| Type | Name | Value |
| --- | --- | --- |
| `TXT` | `_lunary-challenge.example.com` | `lunary-domain-verify=example.com,<token>` |

We check it every few minutes for 48 hours. To check now, `POST /api/v1/domains/example.com/verify` (MCP `verify_domain`). It returns the domain with the outcome in `state` and `last_check`:

- `missing`: no TXT record found yet; propagation can take a few minutes.
- `mismatch`: a TXT record exists with a different value; check it.
- `taken`: another organization has verified this domain.
- `inconclusive`: a public resolver could not be reached; we retry.

Any domain or subdomain you control works (`example.com`, `apps.example.com`). Keep the record: ownership is re-verified every 30 days. A re-verification that fails is retried daily, and seven consecutive failures suspend the domain, which pauses every hostname under it until the record passes again.

### 2. Map hostnames onto instances

Dashboard: the instance's **Settings → Custom hostnames** card, or the **Domains** page (enter `@` for the domain itself). API: `POST /api/v1/instances/{slug}/hostnames` with `{ "hostname": "crm.example.com" }`. MCP: `add_hostname`.

The hostname must be one of your domains or sit under one (it may still be pending, so publish both records at once). The response carries the record to publish in `record`.

A **subdomain** takes a CNAME:

| Type | Name | Value |
| --- | --- | --- |
| `CNAME` | `crm.example.com` | the instance's Lunary address, e.g. `twenty.acme.lunary.cloud` |

Use a **DNS-only** record. A proxied record (Cloudflare's orange cloud) is flattened into address records and can never verify; the check reports it as `flattened`.

The **domain itself** (`example.com`, the apex) cannot carry a CNAME, so it takes A records instead, one per address the instance's Lunary address resolves to. `record.value` lists them, comma-separated, read live from public DNS:

| Type | Name | Value |
| --- | --- | --- |
| `A` | `example.com` | each address of `twenty.acme.lunary.cloud`, e.g. `203.0.113.10` |

If your DNS host offers an ALIAS/ANAME record, pointing one at the Lunary address publishes the same addresses and keeps them current. The check passes while every address at your apex is one of the Lunary address's own; an unrelated address fails it as `mismatch`. Delete any AAAA (IPv6) record at the apex: Lunary addresses are IPv4-only, and the check reports one as `aaaa`. If we ever move the server your instance runs on, we tell you ahead of time so you can update the records. Subdomains follow automatically through their CNAME.

The hostname serves once the record points and the domain is verified; the certificate is issued on the first request. `POST …/hostnames/{hostname}/verify` (MCP `verify_hostname`) checks now. The instance's own `hostnames` field, `GET /api/v1/instances/{slug}/hostnames`, and MCP `list_hostnames` show each hostname's `state` and whether it is `serving`.

- The instance must be publicly exposed.
- A hostname that fails seven consecutive daily pointing checks is suspended and recovers on its own once the record points again.
- `DELETE …/hostnames/{hostname}` (MCP `remove_hostname`) stops serving it; `DELETE /api/v1/domains/{name}` (MCP `remove_domain`) releases the domain and everything under it.

If your DNS zone has CAA records, include `0 issue "letsencrypt.org"`.

## Scopes

Reading domains and hostnames needs `read_instances`; adding, verifying, removing and renaming need `write_instances`.
