CustomDomain Docs

Setup types & the ask gate

How a domain is classified as automatic or manual, how async one-click setup fits in, what proves control for each rail, and how the ask gate guards the edge.

Setup types

POST /v1/domains:check classifies a domain into a setup_type that steers the widget to the smoothest available rail:

  • automatic: the provider can be configured without the customer copying records: an OAuth-into-provider authorization or a one-click setup apply. domains:check reports oauth_available and domain_connect so the widget knows which is on offer.
  • manual: no automatic path; the customer copies the exact records into their DNS provider and the poller watches for them.

setup_type is always automatic or manual. The async flow below is not a setup type but a way to start the one-click rail:

  • async one-click setup: a one-click flow that completes out-of-band (the provider applies the template after a redirect). This is also the rail that powers managed connections: start it with POST /v1/connections/{id}/domainconnect:start and {"managed": true} (or {"rail":"async"}) to get an async consent URL, and the callback (GET /v1/connect/dc/callback) exchanges the code for a durable, encrypted grant so the platform can re-apply and revert the template server-side later. When the provider does not support the async variant, the call falls back to the sync apply_url and the connection stays use-once.

The engine also returns capability flags (ns_support, wildcard_support, cname_flattening, spf_override_support, caa_support) and any record_conflicts it observes in public DNS, so you can pre-warn the customer.

Rail truth: what to actually tell the user

setup_type: automatic means an adapter exists for the provider. It does not mean the end user won't have to type anything: for GoDaddy the adapter needs a Personal Access Token the user creates at GoDaddy. Read the rails block instead:

"rails": {
  "oauth":          { "available": false, "reason": "provider_has_no_oauth" },
  "domain_connect": { "available": false, "reason": "template_not_onboarded" },
  "api_key":        { "available": true,  "caveat": "Needs a GoDaddy Personal Access Token: any account that holds a domain can create one …" },
  "manual":         { "available": true }
},
"recommended_rail": "api_key",
"end_user_automatic": false,
"dashboard_url": "https://dcc.godaddy.com/control/acme.com/dns",
"nameservers": ["ns37.domaincontrol.com", "ns38.domaincontrol.com"]
  • rails.<rail>.available: can this rail run for this domain right now. When false, reason is one machine code: provider_has_no_oauth, oauth_client_not_registered (the provider has a sign-in but this deployment has not switched it on: an operator task; on the hosted service this is the answer for OVHcloud and for every sign-in through a provider's MCP server except Dynadot's and Unstoppable Domains' for now), not_discovered, template_not_onboarded (the provider speaks Domain Connect but hasn't onboarded our template: GoDaddy, IONOS, Cloudflare, Vercel), redirect_unavailable (the provider needs our return link for one-click setup, and this deployment cannot send one), rail_disabled, no_adapter, unknown_provider.
  • caveat: a note on an available rail most users still can't use. Show it; don't treat it as unavailability.
  • recommended_rail: the server's pick, oauth → domain_connect → cloud_browser (the secure sign-in) → api_key → manual (least typing first). The secure sign-in leads a pasted API key wherever it makes the key itself (rails.cloud_browser.key is then set and names what the key can do and when it ends), or the key way is unavailable or carries a caveat. An API key caller whose workspace already keeps a key that can act for the domain gets saved_access first, with rails.saved_access listing the keys; an end user's widget token never gets it. The console and the widget follow it, within the ways each can show, so the surfaces agree.
  • rails.nameservers: when the application offers DNS hosting, the verdict on it for this domain: offered, offered_first (the application's setting is prefer), recommended (no automatic way works) or unavailable, with a reason. Absent while the setting is off.
  • also_provider: set when the registrar and the nameservers name two providers. A domain that was bought at Google Domains is Squarespace's now and keeps Google's nameservers, which Google Cloud DNS zones use too: provider is squarespace (so its secure sign-in is offered) and also_provider is Google Cloud DNS.
  • end_user_automatic: true only when the recommended rail is OAuth or one-click. This is the fact supports_automatic was always misread as.
  • blocked_reason: present when no rail can connect the domain: unregistered (no nameservers: fix at the registrar) or platform_subdomain (www.<name>.vercel.app, which is the platform's DNS, not the user's; POST /v1/connections refuses it with the same code).
  • dashboard_url: a best-effort deep link into the provider's DNS panel, for the "add these records" step.

The legacy domain_connect boolean is derived from the same predicate as rails.domain_connect.available: one-click is advertised only where the provider serves our template, never merely because discovery answered: before offering one-click, the server asks the provider's own API about the exact template the start would send (the answer is cached for five minutes), so the button appears by itself once a provider onboards our template. A deployment can also allowlist providers with DC_SYNC_PROVIDERS.

Control is proven by the rail

On the hosted service there is no separate ownership-TXT challenge (a deployment can switch an ownership challenge on with DOMAIN_AUTHORITY_ENABLED; it is off by default). Separately, a domain that another account already serves is never promoted to live for a second account (domain_already_connected). Each rail is itself proof that the customer controls the zone:

  • an OAuth authorization grants a token for that customer's DNS account (used once, or kept as a managed sign-in grant);
  • a one-click setup apply happens inside the customer's provider session; its async (managed) variant additionally leaves a durable, encrypted grant, used only to re-apply or revert that same template;
  • a secure sign-in happens in the customer's own session at the provider, and the changes can be made only in the account that holds the domain: a sign-in to an account that does not hold it changes nothing and the customer is asked to use another (signed in to the wrong account);
  • an API key is a scoped credential only the zone owner can mint;
  • the manual flow requires the records to actually appear in the domain's authoritative DNS, value-checked;
  • DNS hosting is the owner's own change of the domain's nameservers at its registrar, and the zone turns active only when the registry lists every one of them as ours (Managed DNS).

For one-click setup specifically, the catalog ships a connect-verified template that points the apex A at the CustomDomain™ edge and publishes the ownership TXT token together, applied atomically so serving and proof-of-control go live in one step (a verify-only template publishes just the token). The token record is flagged essential: OnApply, so applying the template that carries it is itself the ownership proof.

Optional credential reuse

Through the API, an API-token credential is used for the apply and then discarded; connect it as a provider account to keep it. In the console, Remember this key for my <provider> account starts ticked, so the key is kept as a provider account unless the user unticks it. Account passwords and tokens that expire within hours are never saved.

A remembered key is encrypted at rest (AES-256-GCM) and its plaintext never leaves the server. On the next connect at that provider, "Use saved key" applies records in one click without re-entering anything, and for Cloudflare, GoDaddy, Route 53, Namecheap, IONOS, united-domains, Epik, NameBright, cPanel hosts and other API key providers that can list the zones a key covers, the same key can bulk-connect the account's other domains. Revoke a saved key any time under Connected accounts in the console sidebar. See Provider setup and Security.

The ask gate

Before the edge serves TLS for a hostname, it calls POST /internal/ask. The control-plane approves the host iff a connection for it is live or propagating, it has not been removed, and its record set includes an edge CNAME/ALIAS or the platform's A records (otherwise the answer carries reason: no_edge_record). It is a status-based gate, answered from a lookup of the connection so the TLS handshake never blocks on business logic. A domain the platform has never connected is refused, so the edge will never obtain a certificate for an unauthorized host.

On this page