CustomDomain Docs
Connections

Create a domain connection (idempotent per app + domain)

Create a domain connection (idempotent per app + domain): CustomDomain™ API reference in the connections group. Covers parameters, request body and responses.

POST
/connections

Starts connecting domain under the calling application and returns the connection plus its authoritative desired record set. Idempotent: if the app already has a non-failed connection for the domain, that existing connection is replayed with 200.

A widget token is never handed another end user's connection. When the application already has a connection for the domain (live, or failed and about to be resumed) that names another end user than the token's end_user_ref, or names one while the token names none, the call answers 403 token_wrong_end_user and shows nothing of it: no id, no field, and a failed connection is not resumed. Mint the token with the end user's end_user_ref. The replay is unchanged for the end user's own token, for a connection that names no end user (an API key made it), for an API key, an app-scoped token and a delegated agent token.

A domain another application already serves live is refused with 409 domain_already_connected before anything is written: move it by disconnecting it there first. When the ownership TXT flow is on the claim is accepted instead, and the ownership challenge decides it.

Callable from the widget (a widget JWT) or straight from your server with an API key, in which case the connection is created on the key's application, or on application_id when that names another application of the same tenant.

A delegated agent token needs domains:connect and creates the connection on its own application. Its answer, a replayed connection included, carries no end_user_ref, the platform's id for its end user. It cannot set the end user's e-mail address: a body with a non-empty end_user_email is refused with 403, and the platform's API key sets it.

A name under a zone CustomDomain™ already hosts joins it without a new move, and the 201 shows it already live when its records resolve. The registrable domain's zone must be active (delegated to us) and hosted for this application. A widget token joins only a zone that is its own by the rule every managed DNS call of a widget token follows: the new connection is the token's, and so is every connection already linked to the zone (a connection that names an end user is the token's when its end_user_ref is the token's; one that names none is the token's only for a name the token is bound to). The connection that started the zone does not decide it, so a zone that an API key, the console or another end user also linked a name to is not the token's. The application must still offer DNS hosting on CustomDomain™'s own DNS. An API key or an app scoped token joins its own application's zone. A delegated agent token, a widget token that does not own the zone, and every other zone change nothing: the name proves control by DNS as always, and no record is written into a zone that is not the caller's. The records written are in the audit log as dns.hosted_zone_joined.

A workspace with no subscription yet connects its first ten domains, or the number an operator set, without one (first_domain_free and free_domains on the billing reads): a retry of the last of them is still replayed with 200, and the next domain is refused with 402 billing_required until the workspace starts its trial. A domain that is still waiting for its records can be removed with DELETE /connections/{id}, which frees its place at once.

Authorization

AuthorizationBearer <token>

Short-lived widget JWT minted server-side via POST /v1/tokens. Scoped to one application (and optionally one domain).

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

The body is decoded with unknown fields REJECTED, so the properties below are exactly the accepted set: any other key is a 400. Everything besides domain is optional.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/connections" \  -H "Content-Type: application/json" \  -d '{    "domain": "string"  }'
{  "id": "string",  "application_id": "string",  "domain": "string",  "host": "string",  "provider_id": "string",  "setup_type": "automatic",  "status": "pending",  "managed": true,  "created_at": "2019-08-24T14:15:22Z",  "sandbox": true,  "last_checked_at": "2019-08-24T14:15:22Z",  "www_redirect": true,  "override_spf": true,  "validate_dmarc": true,  "validate_caa": true,  "monitor": true,  "batch_id": "string",  "end_user_ref": "string",  "client_label": "string",  "retried_at": "2019-08-24T14:15:22Z",  "routing": {    "mode": "edge",    "cname_target": "string",    "apex_ipv4": [      "string"    ],    "set_at": "2019-08-24T14:15:22Z"  },  "records_source": "integrator",  "created_via": "",  "secure_root_domain": true,  "redirect_to": "http://example.com",  "power_root_path_access": [    "string"  ],  "error_code": "string",  "error_message": "string",  "write_rail": "api_key",  "provider_account_id": "string",  "removal_started_at": "2019-08-24T14:15:22Z",  "dns_hosting": {    "state": "none",    "by": "customdomain",    "provider": "string",    "zone": "string",    "since": "2019-08-24T14:15:22Z"  },  "records": [    {      "type": "CNAME",      "host": "string",      "value": "string",      "ttl": 3600,      "priority": 0,      "applied": true,      "change": "created",      "prior": [        {          "type": "string",          "value": "string",          "ttl": 0,          "priority": 0        }      ],      "prior_source": "provider"    }  ],  "end_user_email": "[email protected]",  "ask_end_user_email": true,  "credential": {    "account_id": "string",    "provider": "string",    "status": "active",    "name": "string",    "scopes": [      "string"    ],    "account": "string",    "session_id": "string",    "verified_at": "2019-08-24T14:15:22Z",    "expires_at": "2019-08-24T14:15:22Z",    "notice": "d30",    "reconnect_reason": "expiring",    "reconnect_sent_at": "2019-08-24T14:15:22Z",    "renewed_at": "2019-08-24T14:15:22Z",    "old_key": "revoke_by_hand",    "revoke_url": "http://example.com",    "rights": [      "transfer"    ]  },  "reach": {    "kind": "account",    "account_id": "string",    "label": "string",    "provider": "string",    "can_reapply": true,    "managed_possible": true  }}
{  "id": "string",  "application_id": "string",  "domain": "string",  "host": "string",  "provider_id": "string",  "setup_type": "automatic",  "status": "pending",  "managed": true,  "created_at": "2019-08-24T14:15:22Z",  "sandbox": true,  "last_checked_at": "2019-08-24T14:15:22Z",  "www_redirect": true,  "override_spf": true,  "validate_dmarc": true,  "validate_caa": true,  "monitor": true,  "batch_id": "string",  "end_user_ref": "string",  "client_label": "string",  "retried_at": "2019-08-24T14:15:22Z",  "routing": {    "mode": "edge",    "cname_target": "string",    "apex_ipv4": [      "string"    ],    "set_at": "2019-08-24T14:15:22Z"  },  "records_source": "integrator",  "created_via": "",  "secure_root_domain": true,  "redirect_to": "http://example.com",  "power_root_path_access": [    "string"  ],  "error_code": "string",  "error_message": "string",  "write_rail": "api_key",  "provider_account_id": "string",  "removal_started_at": "2019-08-24T14:15:22Z",  "dns_hosting": {    "state": "none",    "by": "customdomain",    "provider": "string",    "zone": "string",    "since": "2019-08-24T14:15:22Z"  },  "records": [    {      "type": "CNAME",      "host": "string",      "value": "string",      "ttl": 3600,      "priority": 0,      "applied": true,      "change": "created",      "prior": [        {          "type": "string",          "value": "string",          "ttl": 0,          "priority": 0        }      ],      "prior_source": "provider"    }  ],  "end_user_email": "[email protected]",  "ask_end_user_email": true,  "credential": {    "account_id": "string",    "provider": "string",    "status": "active",    "name": "string",    "scopes": [      "string"    ],    "account": "string",    "session_id": "string",    "verified_at": "2019-08-24T14:15:22Z",    "expires_at": "2019-08-24T14:15:22Z",    "notice": "d30",    "reconnect_reason": "expiring",    "reconnect_sent_at": "2019-08-24T14:15:22Z",    "renewed_at": "2019-08-24T14:15:22Z",    "old_key": "revoke_by_hand",    "revoke_url": "http://example.com",    "rights": [      "transfer"    ]  },  "reach": {    "kind": "account",    "account_id": "string",    "label": "string",    "provider": "string",    "can_reapply": true,    "managed_possible": true  }}
{  "code": "string",  "title": "string",  "details": "string"}
{  "code": "string",  "title": "string",  "details": "string"}
{  "code": "billing_required",  "title": "An active subscription is needed to connect new domains.",  "details": "This workspace has not started its subscription yet. Domains that are already connected keep working. An owner or admin can set up billing at https://app.customdomain.ai/app/billing."}
{  "code": "string",  "title": "string",  "details": "string"}
{  "code": "string",  "title": "string",  "details": "string"}
{  "code": "string",  "title": "string",  "details": "string"}