> For the complete documentation index, see [llms.txt](https://docs.statuspal.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.statuspal.io/guides/platform/user-guides/custom-domains-api-guide.md).

# Custom Domains API Guide

This guide walks through configuring a custom domain for a StatusPal status page entirely via the REST API. By the end, your status page will be reachable at a domain you own (e.g. `status.example.com`).

## Prerequisites

| Requirement             | Details                                                                                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| StatusPal API key       | Either an **Organization API key** (prefixed `ok_`) or a **User API key** (prefixed `uk_`). Found under **Organization Settings → API Keys** or **User Profile → API Key**. |
| Organization ID         | The numeric ID of your StatusPal organization.                                                                                                                              |
| An existing status page | You need the **subdomain** (slug) of the status page you want to attach the custom domain to.                                                                               |
| DNS provider access     | You must be able to create CNAME and TXT records on the domain you want to use.                                                                                             |

All API requests use the base URL:

```bash
https://statuspal.eu/api/v2
```

{% hint style="info" %}
US-region accounts use `https://statuspal.us/api/v2` instead.
{% endhint %}

Authentication is via the `Authorization` header:

```bash
Authorization: <your-api-key>
```

## Overview

StatusPal supports two CDN providers for custom domains: **Cloudflare** and **Bunny**. The provider handles SSL certificate issuance and edge routing. The general flow is:

{% stepper %}
{% step %}

### Enable the custom domain

Configure the custom domain on the status page via the API.
{% endstep %}

{% step %}

### Retrieve DNS records

Get the DNS records StatusPal needs you to create.
{% endstep %}

{% step %}

### Create DNS records

Create those DNS records at your DNS provider.
{% endstep %}

{% step %}

### Wait for activation

Poll the status page until the domain becomes `active`.
{% endstep %}
{% endstepper %}

The Cloudflare provider requires up to three DNS records (CNAME + two TXT records for hostname and certificate verification). The Bunny provider requires only a CNAME record.

## Step 1 — Enable the custom domain

Update the status page with a `domain_config` object:

```bash
curl -X PUT "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status_page": {
      "domain_config": {
        "provider": "cloudflare",
        "domain": "status.example.com"
      }
    }
  }'
```

### Parameters

| Field                    | Type   | Required | Description                                                       |
| ------------------------ | ------ | -------- | ----------------------------------------------------------------- |
| `domain_config.provider` | string | Yes      | `"cloudflare"` or `"bunny"`                                       |
| `domain_config.domain`   | string | Yes      | The custom domain (e.g. `status.example.com`). Must be lowercase. |

### Response

The response includes the full status page object. The `domain_config` field will show the initial state:

```json
{
  "status_page": {
    "subdomain": "my-page",
    "domain": "status.example.com",
    "custom_domain_enabled": true,
    "domain_config": {
      "provider": "cloudflare",
      "domain": "status.example.com",
      "status": "configuring",
      "main_hostname": null,
      "validation_records": null,
      "external_id": null,
      "error": null,
      "pullzone_id": null
    }
  }
}
```

StatusPal begins provisioning the domain asynchronously. The `status` field starts as `"configuring"`.

## Step 2 — Retrieve DNS records

Poll the status page until `validation_records` are populated:

```bash
curl "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>"
```

The response `domain_config` will eventually include the DNS records you need:

### Cloudflare provider

```json
{
  "domain_config": {
    "provider": "cloudflare",
    "domain": "status.example.com",
    "status": "configuring",
    "validation_records": {
      "hostname_cname_name": "status.example.com",
      "hostname_cname_value": "sp-abc123.statuspal.io",
      "hostname_txt_name": "status.example.com",
      "hostname_txt_value": "abc123-verification-string",
      "certificate_txt_name": "_acme-challenge.status.example.com",
      "certificate_txt_value": "xyz789-challenge-string"
    },
    "main_hostname": "sp-abc123.statuspal.io",
    "external_id": "cf-hostname-id"
  }
}
```

### Bunny provider

```json
{
  "domain_config": {
    "provider": "bunny",
    "domain": "status.example.com",
    "status": "configuring",
    "validation_records": {
      "hostname_cname_name": "status.example.com",
      "hostname_cname_value": "sp-abc123.b-cdn.net"
    },
    "main_hostname": "sp-abc123.b-cdn.net",
    "external_id": "status.example.com"
  }
}
```

{% hint style="info" %}
The `certificate_txt_*` fields for Cloudflare may not appear immediately. Poll every 10–15 seconds until they are present before proceeding to Step 3.
{% endhint %}

## Step 3 — Create DNS records

Create the DNS records at your DNS provider. The exact records depend on the CDN provider.

### Cloudflare provider — 3 records

| Type    | Name                            | Value                            | Purpose                                   |
| ------- | ------------------------------- | -------------------------------- | ----------------------------------------- |
| `CNAME` | `status.example.com`            | value of `hostname_cname_value`  | Routes traffic to StatusPal               |
| `TXT`   | value of `hostname_txt_name`    | value of `hostname_txt_value`    | Hostname ownership verification           |
| `TXT`   | value of `certificate_txt_name` | value of `certificate_txt_value` | SSL certificate issuance (ACME challenge) |

{% hint style="warning" %}
If your DNS provider is Cloudflare (for DNS management, not the StatusPal CDN provider), make sure the CNAME record has **proxy disabled** (DNS-only / grey cloud) to avoid routing conflicts.
{% endhint %}

### Bunny provider — 1 record

| Type    | Name                 | Value                           | Purpose                     |
| ------- | -------------------- | ------------------------------- | --------------------------- |
| `CNAME` | `status.example.com` | value of `hostname_cname_value` | Routes traffic to StatusPal |

#### Example using the Cloudflare DNS API

```bash
# CNAME record
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
  -H "Authorization: Bearer <cloudflare-api-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "CNAME",
    "name": "status.example.com",
    "content": "sp-abc123.statuspal.io",
    "proxied": false
  }'

# Hostname TXT record
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
  -H "Authorization: Bearer <cloudflare-api-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "TXT",
    "name": "status.example.com",
    "content": "abc123-verification-string"
  }'

# Certificate TXT record
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
  -H "Authorization: Bearer <cloudflare-api-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "TXT",
    "name": "_acme-challenge.status.example.com",
    "content": "xyz789-challenge-string"
  }'
```

## Step 4 — Wait for activation

After DNS records are created, poll the status page until `domain_config.status` becomes `"active"`:

```bash
curl "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>"
```

### Domain status values

| Status                | Meaning                                                               |
| --------------------- | --------------------------------------------------------------------- |
| `configuring`         | StatusPal is provisioning the domain or waiting for DNS verification. |
| `active`              | The domain is fully configured and serving your status page with SSL. |
| `failed_to_configure` | Something went wrong. Check the `error` field for details.            |
| `disabled`            | No custom domain is configured.                                       |

The verification process typically takes 1–10 minutes depending on DNS propagation. Poll every 10–30 seconds.

Once the status is `"active"`, your status page is live at `https://status.example.com`.

## Removing a custom domain

To remove the custom domain, update `domain_config` with `provider` set to `null`:

```bash
curl -X PUT "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status_page": {
      "domain_config": {
        "provider": null,
        "domain": null
      }
    }
  }'
```

This revokes the SSL certificate and removes the domain from the CDN. Remember to clean up the DNS records at your DNS provider.

## Retrying a failed configuration

If `domain_config.status` is `"failed_to_configure"`, you can retry by re-submitting the same `domain_config`:

```bash
curl -X PUT "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status_page": {
      "domain_config": {
        "provider": "cloudflare",
        "domain": "status.example.com"
      }
    }
  }'
```

This resets the status to `"configuring"` and restarts the provisioning process.

## Changing the custom domain

To switch to a different domain, update `domain_config` with the new domain:

```bash
curl -X PUT "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages/{subdomain}" \
  -H "Authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status_page": {
      "domain_config": {
        "provider": "cloudflare",
        "domain": "new-status.example.com"
      }
    }
  }'
```

StatusPal will automatically revoke the old domain's certificate and begin provisioning the new one. Follow Steps 2–4 again for the new domain.

## Setting up a custom domain on a new status page

You can configure the custom domain at creation time:

```bash
curl -X POST "https://statuspal.eu/api/v2/orgs/{organization_id}/status_pages" \
  -H "Authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status_page": {
      "name": "My Status Page",
      "url": "example.com",
      "time_zone": "UTC",
      "domain_config": {
        "provider": "cloudflare",
        "domain": "status.example.com"
      }
    }
  }'
```

Then follow Steps 2–4 to complete the DNS setup.
