# Custom domains and TLS

> Put your own domain on an app. Add it, set the DNS records the answer names, and Rytiv proves it, issues the certificate and renews it.

Every app answers on `https://<app>.rytiv.app` with a certificate from the start. A custom domain is added to an app, proven by a DNS record, pointed at Rytiv, and gets its own certificate. Wildcards are not accepted for a single domain; a whole zone can be proven once instead.

## 1. Add the domain

**CLI**

```bash
ry domains add web www.example.com
```

**curl**

```bash
curl -X POST "https://api.rytiv.app/v1/apps/web/domains" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "www.example.com"}'
```

**TypeScript**

```ts
const res = await fetch("https://api.rytiv.app/v1/apps/web/domains", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ domain: "www.example.com" }),
});
const { status, dns } = await res.json(); // dns.txt and dns.point: the records to set
```

**Python**

```python
import os, requests

domain = requests.post(
    "https://api.rytiv.app/v1/apps/web/domains",
    headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"},
    json={"domain": "www.example.com"},
).json()
print(domain["status"], domain["dns"])
```

The answer is the domain in state `pending_dns` with the records to set.

## 2. Set the DNS records

Two things have to be true: you own the domain, and it points at Rytiv.

| Record | Name | Value | Proves |
| --- | --- | --- | --- |
| `TXT` | `_rytiv.www.example.com` | `rytiv-verify-<token>` from the answer | ownership |
| `CNAME` | `www.example.com` | `<slug>.rytivdns.com` (or `web.rytiv.app`) | ownership and traffic |
| `A` | `example.com` | the ingress addresses from the answer | ownership and traffic, for an apex |

A subdomain needs the CNAME (the TXT record is offered as well); an apex such as `example.com` cannot carry a CNAME and gets A records instead. The exact names and values are in the answer of `ry domains add` and of `GET /v1/apps/:name/domains`; copy them from there.

## 3. Wait, or check now

Rytiv checks the records every 30 seconds at first, then with a growing gap up to an hour. `ry domains check web www.example.com` asks for a check right away.

| Status | Meaning |
| --- | --- |
| `pending_dns` | Waiting for the records. |
| `verified` | Ownership is proven; the certificate is being ordered. |
| `cert_issued` | The certificate exists. |
| `active` | The certificate exists and a machine of the app is ready to answer. |
| `error` | The certificate could not be issued; `error` says why. A missing DNS record never leads here, it keeps the domain `pending_dns`. |

```bash
ry domains list web
```

The list shows the status, the days left on the certificate and the records still missing.

## Certificates

Rytiv orders the certificate over ACME with an HTTP challenge that the edge answers, as soon as ownership is proven. It is renewed when fewer than 30 days remain. The default address `https://<app>.rytiv.app` is covered by the platform's own certificate. Port 80 answers a redirect to HTTPS.

## Redirect one domain to another

```bash
ry domains redirect web example.com www.example.com
ry domains redirect web example.com --off
```

The first domain then answers `308` to the second, keeping path and query. The target must be another domain of the same app or its default address; a target cannot itself redirect, and a domain others point at cannot start redirecting, so there are no chains and no loops.

## Prove a whole zone once

```bash
ry domains zones add example.com
```

The answer names two CNAME records: `_acme-challenge.example.com` to a challenge name Rytiv gives you, and `*.example.com` to Rytiv's ingress. Once they resolve, every name directly below the zone (`app.example.com`, `api.example.com`) is yours without a record of its own and is covered by one wildcard certificate; `ry domains add web app.example.com` then needs no DNS change. Only one level below the zone is covered.

## Remove

```bash
ry domains remove web www.example.com
```

The CLI asks you to type the name; pass `--yes` in scripts. The certificate is not revoked, the domain simply stops routing.

## Errors you will meet

| Status | Code | When |
| --- | --- | --- |
| `400` | `bad-request` | The name is not a domain: a wildcard, an IP, a single label, a platform domain. The sentence says which. |
| `409` | `shadows-default-address` | The domain is the default address of an app. |
| `409` | `mail-domain` | The name is a sending domain of a zone and cannot become an app domain. |
| `409` | `conflict` | The domain is already claimed by someone. |
