# Custom domains

Source: https://developers.leonai.io/docs/guides/domains/

Serve your short links from your own branded domain (for example `go.example.com`
instead of `short.leonai.io`). Domain endpoints accept a **JWT** or an **API key** with
`domains:read` / `domains:write`. Your domain quota depends on your
[tier](https://developers.leonai.io/docs/intro/#subscription-tiers) (Free 1, Pro 5, Business 25, Enterprise
unlimited).

There are two ways to connect a domain: **one-click Cloudflare auto-connect**, or the
**manual** DNS path that works with any provider.

## Add the domain

Both flows start by registering the hostname:

```bash
curl -X POST https://api.short.leonai.io/api/v1/domains \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "go.example.com" }'
```

`REST: POST /api/v1/domains` → `201`. The response includes the domain record plus a
`verificationToken`, `verificationInstructions` (a TXT record), and a best-effort
`detection` of your DNS provider:

```json
{
  "success": true,
  "data": {
    "id": "dom_...",
    "domain": "go.example.com",
    "verified": false,
    "sslEnabled": false,
    "verificationToken": "leonai-verify-...",
    "verificationInstructions": {
      "type": "TXT",
      "name": "_leonai-verify",
      "value": "leonai-verify-...",
      "example": "_leonai-verify.go.example.com TXT leonai-verify-..."
    },
    "detection": { "provider": "cloudflare", "autoConnectSupported": true }
  }
}
```

## One-click Cloudflare auto-connect

If your domain's authoritative nameservers are Cloudflare, LeonAI can create all the
DNS records and enable SSL for you with a scoped Cloudflare API token.

### 1. Detect the provider

```bash
curl https://api.short.leonai.io/api/v1/domains/<id>/detect-provider \
  -H "Authorization: Bearer <jwt>"
```

`REST: GET /api/v1/domains/:id/detect-provider`. Response:

```json
{
  "success": true,
  "data": {
    "provider": "cloudflare",
    "nameservers": ["gina.ns.cloudflare.com", "rick.ns.cloudflare.com"],
    "autoConnectSupported": true
  }
}
```

`provider` is one of `cloudflare`, `vercel`, `other`, or `unknown`. **Auto-connect is
supported only when `autoConnectSupported` is `true` (Cloudflare).** Everything else
uses the [manual path](#manual-dns-setup-any-provider).

### 2. Create a scoped Cloudflare token

In the Cloudflare dashboard, create an API token with **Zone → DNS → Edit** permission,
scoped to the single zone for this domain. LeonAI stores the token **encrypted** and
never returns or logs it.

### 3. Auto-configure

```bash
curl -X POST https://api.short.leonai.io/api/v1/domains/<id>/auto-configure \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "apiToken": "<cloudflare-scoped-token>" }'
```

`REST: POST /api/v1/domains/:id/auto-configure`. Body fields:

| Field      | Required | Notes                                                        |
| ---------- | -------- | ------------------------------------------------------------ |
| `apiToken` | yes      | Scoped Cloudflare token (`Zone.DNS:Edit`). Stored encrypted. |
| `provider` | no       | Only `cloudflare` is supported (the default).                |
| `zoneId`   | no       | Auto-resolved from the token when omitted.                   |

LeonAI resolves the zone, then automatically creates:

- a **TXT** record `_leonai-verify.<domain>` = your `verificationToken` (ownership),
- a **CNAME** `<domain>` → the LeonAI edge target, **proxied (orange-cloud)** so
  Cloudflare terminates **SSL automatically**.

It then runs verification immediately and marks the domain `verified` and
`sslEnabled`. Response:

```json
{
  "success": true,
  "message": "Domain connected",
  "data": {
    "id": "dom_...",
    "domain": "go.example.com",
    "verified": true,
    "sslEnabled": true,
    "connection": { "provider": "cloudflare", "verified": true, "sslEnabled": true }
  }
}
```

:::note[Encryption key required]
Auto-connect requires a DNS encryption key on the server (`DNS_ENCRYPTION_KEY`, falling
back to `TOTP_ENCRYPTION_KEY`). Without it the endpoint returns `503`. Token/permission
errors from Cloudflare map to `403`, a missing zone to `404`.
:::

## Manual DNS setup (any provider)

### 1. Get the records for your provider

```bash
curl https://api.short.leonai.io/api/v1/domains/<id>/setup/<providerId> \
  -H "Authorization: Bearer <jwt>"
```

`REST: GET /api/v1/domains/:id/setup/:providerId` returns provider-specific
instructions with three records:

| Record   | Type  | Host             | Value                    |
| -------- | ----- | ---------------- | ------------------------ |
| Verify   | TXT   | `_leonai-verify` | your `verificationToken` |
| Redirect | CNAME | `@` (root)       | the LeonAI edge target   |
| WWW      | CNAME | `www`            | the LeonAI edge target   |

List the providers LeonAI knows about with `GET /api/v1/domains/providers`.

### 2. Verify

After adding at least the TXT record, trigger verification:

```bash
curl -X POST https://api.short.leonai.io/api/v1/domains/<id>/verify \
  -H "Authorization: Bearer <jwt>"
```

`REST: POST /api/v1/domains/:id/verify`. LeonAI reads `_leonai-verify.<domain>` and
compares it to the stored token. On success the domain becomes `verified` and a
`domain.verified` [webhook](https://developers.leonai.io/docs/guides/webhooks/) fires (once, on the first successful
check). On mismatch you get a `400` with the record it found
and the value it expected.

## Manage domains

| Method + path                | Scope           | Notes                                       |
| ---------------------------- | --------------- | ------------------------------------------- |
| `GET /api/v1/domains`        | `domains:read`  | List domains with `linksCount`              |
| `GET /api/v1/domains/:id`    | `domains:read`  | Domain details                              |
| `DELETE /api/v1/domains/:id` | `domains:write` | Remove a domain (409 if links still use it) |

Once verified, set a link's `domainId` at creation to serve it from your domain — see
the [Links guide](https://developers.leonai.io/docs/guides/links/).
