# Add a custom domain

> Serve an app on your own domain once you prove it is yours with a TXT record and point it at the app.

Source: https://docs.coritan.com/apps/custom-domains/

In the dashboard:

- /dashboard/apps/…/domains: https://www.coritan.com/dashboard/apps

Every app answers at its platform name. Add a custom domain, such as `www.example.com`, to serve the app on your own name as well. You prove the domain is yours with a TXT record, point it at the app with a CNAME record, and we issue its certificate. The app's **Domains** tab lists its domains and gives the records to publish.

## Before you begin

- You need a domain whose DNS records you can change at your DNS host. If Coritan hosts its DNS, see [Add, edit and delete DNS records](/websites/dns/manage-dns-records/).
- For an organization's app, you need the owner or admin role in the organization.
- An app can have up to 20 custom domains. The **Domains** tab says how many it uses, such as `1 of 20 custom domains in use.`

## The platform name

When you create an app, we give it a platform name made of its name on the platform and the platform's app domain, such as `web-shop.apps.example.net`. It is verified from the start, it goes with the app, and you cannot remove it. The **Domains** tab lists it first, as `Platform address`. Names under the app domain are only given out this way, so you cannot add one as a custom domain. When the platform has no app domain, apps get no platform name and serve only their custom domains.

## Add and verify a domain

1. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps) and select the **Domains** tab.
2. Select **Add domain…**. In **Domain**, type the hostname, such as `www.example.com` or `example.com`, without `https://` or a path. Wildcard names such as `*.example.com` are not accepted. Select **Add domain**.

   A message confirms `www.example.com added.`, and the domain's row shows **Pending verification**.
3. Under the row, **Prove the domain is yours** gives the TXT record. Publish it at your DNS host:

   ```dns
   _coritan-app.www.example.com.  3600  IN  TXT    "8b7c1d0e2f3a4b5c6d7e8f9012345678"
   ```

   The name is `_coritan-app.` followed by your hostname, and the value is the token we gave the domain. Each has a copy button.
4. Under that, **Send its visitors to the app** gives the CNAME record that points the hostname at the app. Its **Target** is the app's platform name:

   ```dns
   www.example.com.  3600  IN  CNAME  web-shop.apps.example.net.
   ```

   At the top of a zone, such as `example.com`, a CNAME is not allowed. Use your DNS host's ALIAS record or CNAME flattening to the same target.
5. Select **Verify** on the domain's row. When we find the TXT record, a message confirms `www.example.com verified.`, and the domain starts serving the app. When we do not, `Not verified yet` under the row says which record we looked for. Wait a few minutes for DNS to update, then select **Verify** again.

The domain gets no route until it is verified, so nothing reaches the app on it before then. You can remove the TXT record once the domain is verified.

## Read the domain list

**Domain**
: The hostname, and whether it is the `Platform address` or a `Custom domain`. A verified domain's name opens the app in a new tab, and a verified custom domain also shows its CNAME target.

**Status**
: **Verified** with its certificate, or **Pending verification** with `Waiting for its TXT record`.

**Added**
: When the domain was added.

The menu at the end of each row has **Open in a new tab** for a verified domain, **Copy address**, and **Remove domain…** for a custom domain.

## Certificates

A verified domain's certificate is ordered once the app has replicas serving it. It moves from `No certificate yet` to `Certificate on its way` to `Certificate issued`, which the API sends as `none`, `pending` and `issued`. The certificate authority checks the domain over HTTP, so the CNAME record from step 4 must be in place for the certificate to be issued.

## A hostname in use elsewhere

A hostname can belong to one app at a time. Another app that is still verifying a hostname holds it for 72 hours after adding it. After that, the next app that adds the hostname takes it over. A hostname that another app has verified, or that another product on the platform serves, such as a [web proxy](/proxies/web-proxies/), cannot be added.

## Remove a domain

Removing a custom domain stops it reaching the app at once, and deletes its route and certificate. The records at your DNS host stay until you delete them. The platform name cannot be removed.

1. On the **Domains** tab, open the menu at the end of the domain's row and select **Remove domain…**.
2. Select **Remove domain** to confirm.

A message confirms `www.example.com removed.`

## Result

The domain shows **Verified**, and `Certificate issued` once the certificate authority has checked it. Visitors reach the app at `https://www.example.com`.

## Troubleshooting

`No TXT record at _coritan-app.www.example.com holds the token yet. DNS changes can take a few minutes to appear.`
: The record is not published yet, or holds another value. Check its name and value at your DNS host, wait a few minutes, then check again.

`That hostname is in use on the platform`
: Another app has verified it, or added it less than 72 hours ago. If it is yours, remove it from the other app first.

`That hostname is already served by another route on the platform`
: Another product serves it, such as a web proxy. Remove it there first.

`Names under apps.example.net are given to apps by the platform`
: The hostname is under the platform's app domain. Each app gets its own name there when it is created.

`An app can have at most 20 custom domains`
: Remove a domain the app no longer needs. While the app has 20, **Add domain…** is turned off.

The certificate stays `Certificate on its way`
: The hostname does not point at the app yet. Check the CNAME record, or the ALIAS record at the top of a zone.

`Too many checks for this app. Wait a few minutes; DNS changes take a while anyway.`
: The app asked for 30 checks in the last 10 minutes. Wait, then check again.

## Related

- [How Apps works](/apps/how-apps-work/#going-live)
- [Add, edit and delete DNS records](/websites/dns/manage-dns-records/)
- [Apps limits](/apps/limits/)

## With the API

The hostname in a path is the domain's `hostname`, such as `www.example.com`. For an organization's app, send the same requests under `/api/v1/orgs/{org_slug}/apps` ([organization apps reference](/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-domains)).

### List domains

[`GET /api/v1/client/apps/{app_uuid}/domains`](/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-domains) lists the platform name first, then the custom domains:

```bash
curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

```json
{
  "domains": [
    {
      "hostname": "web-shop.apps.example.net",
      "kind": "platform",
      "verified": true,
      "verified_at": "2026-09-20T09:12:00Z",
      "created_at": "2026-09-20T09:12:00Z",
      "url": "https://web-shop.apps.example.net",
      "certificate": "issued",
      "verification": null,
      "cname_target": null
    },
    {
      "hostname": "www.example.com",
      "kind": "custom",
      "verified": false,
      "verified_at": null,
      "created_at": "2026-09-26T10:05:00Z",
      "url": null,
      "certificate": "none",
      "verification": {"type": "TXT", "name": "_coritan-app.www.example.com", "value": "8b7c1d0e2f3a4b5c6d7e8f9012345678"},
      "cname_target": "web-shop.apps.example.net"
    }
  ]
}
```

`kind` is `platform` or `custom`. `url` is `null` until the domain is verified. `certificate` is `none`, `pending` or `issued`. `verification` is the TXT record to publish, and `null` once the domain is verified. `cname_target` is where a custom domain points, and `null` for the platform name.

### Add a domain through the API

[`POST /api/v1/client/apps/{app_uuid}/domains`](/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains) adds a custom domain and answers `201` with it, in the shape above:

```bash
curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "www.example.com"}'
```

| Status | `detail` | Cause |
| --- | --- | --- |
| `409` | `{"error": "domain_exists", "message": "That hostname is already added to this app"}` | The app has it already. |
| `409` | `{"error": "hostname_in_use", "message": "That hostname is in use on the platform"}` | Another app holds it, or another product serves it. |
| `409` | `{"error": "domain_limit", "message": "An app can have at most 20 custom domains"}` | The app has 20 custom domains. |
| `422` | `{"message": "Give the hostname alone, without http:// or a path", "field": "hostname"}` | `hostname` starts with `http://` or `https://`. |
| `422` | `{"message": "'*.example.com' is not a hostname", "field": "hostname"}` | `hostname` is not a hostname. |
| `422` | `{"message": "Names under apps.example.net are given to apps by the platform", "field": "hostname"}` | The name is under the app domain. |

### Verify a domain through the API

[`POST /api/v1/client/apps/{app_uuid}/domains/{hostname}/verify`](/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains-hostname-verify) looks for the TXT record:

```bash
curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains/www.example.com/verify \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

When it finds the record, it answers `{"domain": {...}, "verified": true, "certificates": ["www.example.com"]}`, where `certificates` names the hostnames whose certificate it ordered. When it does not, it answers `200` with `"verified": false` and a `message` saying which record it looked for. A domain that is already verified answers `"verified": true` with an empty `certificates`.

It answers `409` with `hostname_in_use` when another product took the hostname meanwhile, and `429` after 30 checks for the app in 10 minutes.

### Remove a domain through the API

[`DELETE /api/v1/client/apps/{app_uuid}/domains/{hostname}`](/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-domains-hostname) removes a custom domain and answers `{"deleted": "www.example.com"}`. The platform name answers `409` with `{"error": "platform_domain", "message": "The platform name goes with the app and cannot be removed"}`, and a hostname the app does not have answers `404` with `Domain not found`.

## API

- `GET /api/v1/client/apps/{app_uuid}/domains`: List an app's domains: its platform name first, then its custom domains (https://docs.coritan.com/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-domains)
- `POST /api/v1/client/apps/{app_uuid}/domains`: Add a custom domain (https://docs.coritan.com/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains)
- `DELETE /api/v1/client/apps/{app_uuid}/domains/{hostname}`: Remove a custom domain from an app (https://docs.coritan.com/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-domains-hostname)
- `POST /api/v1/client/apps/{app_uuid}/domains/{hostname}/verify`: Look for the domain's TXT record (https://docs.coritan.com/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains-hostname-verify)
- `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains`: List an app's domains: its platform name first, then its custom domains (https://docs.coritan.com/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-domains)
- `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains`: Add a custom domain (https://docs.coritan.com/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-domains)
- `DELETE /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains/{hostname}`: Remove a custom domain from an app (https://docs.coritan.com/api/reference/organizations/organization-apps/#op-delete-api-v1-orgs-org-slug-apps-app-uuid-domains-hostname)
- `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains/{hostname}/verify`: Look for the domain's TXT record (https://docs.coritan.com/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-domains-hostname-verify)
