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:checkreportsoauth_availableanddomain_connectso 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:startand{"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 syncapply_urland 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,reasonis 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.keyis 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 getssaved_accessfirst, withrails.saved_accesslisting 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 isprefer),recommended(no automatic way works) orunavailable, with areason. 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:providerissquarespace(so its secure sign-in is offered) andalso_provideris Google Cloud DNS.end_user_automatic: true only when the recommended rail is OAuth or one-click. This is the factsupports_automaticwas always misread as.blocked_reason: present when no rail can connect the domain:unregistered(no nameservers: fix at the registrar) orplatform_subdomain(www.<name>.vercel.app, which is the platform's DNS, not the user's;POST /v1/connectionsrefuses 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.