---
title: "Connect a custom domain."
method: POST
path: "/v1/domains"
tags: ["Domains"]
---

# Connect a custom domain.

`POST /v1/domains`

## Request

- Base URL: `https://api.domainee.dev`
- URL: `https://api.domainee.dev/v1/domains`
- Auth: HTTP bearer

Registers the hostname, runs a preflight check, and returns the DNS records
the domain owner has to create. The domain stays `pending` until those records
resolve; SSL is issued automatically once they do.

Requires an active subscription on the workspace even while inside the free
tier, so a card must be on file.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `hostname` string, required: The customer's hostname. Normalized and validated server-side.
  - `originUrl` string, uri, required: Where verified traffic should go.
  - `metadata` object
  - `mode` 'proxy' | 'redirect'
  - `keepHost` boolean
  - `redirectWww` boolean
  - `redirectStatus` 301 | 302

## Response `201`

Domain created. Follow `dnsRecords` to finish verification.

- object
  - `domain` Domain, required: A customer hostname connected to one of your origins.
    - `id` string, required: Domainee identifier.
    - `workspaceId` string, required: Workspace the domain belongs to.
    - `hostname` string, required
    - `originUrl` string, uri, required: Where verified traffic is sent.
    - `metadata` object: Opaque key/value bag echoed back on reads and webhooks.
    - `mode` 'proxy' | 'redirect': `proxy` serves the origin under the custom hostname; `redirect` sends a 301/302.
    - `keepHost` boolean: Forward the original Host header to the origin instead of rewriting it.
    - `redirectWww` boolean: Also answer for the www. variant.
    - `redirectStatus` 301 | 302
    - `status` 'pending' | 'verified' | 'failed' | 'expired', required: Verification state. Traffic only flows once `verified`.
    - `dnsRecords` DnsRecordInstruction[]: The records the domain owner must create for verification to pass. Always the single CNAME. Unchanged and safe to keep rendering as-is; read `dnsRecordOptions` if you want to offer the apex A-record path.
      - `name` string, required
      - `value` string, required
      - `type` string, required
      - `purpose` string
    - `dnsRecordOptions` DnsRecordSet[]: Every supported way to point this hostname at the edge, recommended first — take `[0]` to get the right one. For a subdomain that is the `cname` set. For an apex it is the `a` set: a root domain cannot hold a CNAME, and the ALIAS/ANAME/flattening substitutes all pin the name to a single region with no failover, whereas the anycast A records keep every region.
      - `method` 'cname' | 'a', required: `cname` works wherever the provider allows a CNAME at this position. `a` is for apex/root domains on providers with no ALIAS or ANAME support.
      - `label` string, required
      - `recommended` boolean, required
      - `note` string
      - `records` DnsRecordInstruction[], required
        - `name` string, required
        - `value` string, required
        - `type` string, required
        - `purpose` string
    - `verificationToken` string
    - `verifiedAt` string, date-time, nullable
    - `lastCheckedAt` string, date-time, nullable
    - `isResolving` boolean: The hostname resolves in public DNS.
    - `pointsToEdge` boolean: It resolves to Domainee's edge.
    - `dnsPointedAt` string[]: What the hostname currently resolves to.
    - `sslActiveFrom` string, date-time, nullable
    - `sslActiveUntil` string, date-time, nullable
    - `monitorStatus` 'unknown' | 'dns_not_resolving' | 'dns_incorrect' | 'pending_ssl' | 'active_ssl' | 'target_not_loading' | 'ssl_failed' | 'ssl_expired': Live health, refreshed by the DNS monitor and by POST /v1/domains/{id}/check.
    - `monitorMessage` string, nullable
    - `lastMonitoredAt` string, date-time, nullable
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time
  - `warnings` PreflightWarning[]: Non-blocking preflight notes, e.g. an existing CAA record.
    - `code` 'caa_blocks_lets_encrypt' | 'dns_unresolvable' | 'origin_unreachable' | 'origin_ssrf_blocked', required
    - `message` string, required
    - `caaRecords` string[]: Present on `caa_blocks_lets_encrypt`.
    - `details` string: May be present on `origin_unreachable`.
    - `resolvedIps` string[]: Present on `origin_ssrf_blocked`.

## Other responses

- `400`: Invalid hostname, reserved hostname, or a blocking preflight failure.
- `401`: Missing, malformed, revoked, or expired Bearer token.
- `402`: The workspace has no active subscription or the charge failed.
- `409`: That hostname is already connected.
- `429`: More than 60 requests in a minute for this key.

---

[API](https://skmtc.dev/domainee/apis/domainee-api.md) · [All operations](https://skmtc.dev/domainee/apis/domainee-api/llms.txt) · [OpenAPI document](https://skmtc.dev/domainee/apis/domainee-api/revisions/756d54434372?raw)
