Setup types & the ask gate
How a domain is classified, the four connect rails, and the status-based edge authorization gate.
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.
- async — a one-click setup 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. - mcp — reserved for agent-driven setup.
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 is
an API key that GoDaddy only issues to accounts with 10+ domains or a paid
plan. 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": "GoDaddy only issues API access to accounts with 10+ domains or a paid plan; …" },
"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(an operator task on your deployment),not_discovered,template_not_onboarded(the provider speaks Domain Connect but hasn't onboarded our template — GoDaddy, IONOS, Cloudflare),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→api_key→manual(least typing first). The console and the widget follow it, so all three surfaces agree.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— 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 is measured to serve our template (the DC_SYNC_PROVIDERS
allowlist), never merely because discovery answered.
Control is proven by the rail
There is no separate ownership-TXT challenge. Each rail is itself proof that the customer controls the zone:
- an OAuth authorization grants a one-time token to that customer's DNS account;
- a one-click setup apply happens inside the customer's provider session; its async (managed) variant additionally leaves a durable, encrypted grant — the one credential the platform keeps, and only to re-apply or revert that same template;
- an API token 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.
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
By default an API-token credential is used once for the apply and then discarded. Optionally, when connecting in the console you can tick "Remember this key" to save that provider key for reuse. It is off by default and opt-in per key.
A remembered key is encrypted at rest (AES-256-GCM, scoped to one tenant + provider) 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 the same key can bulk-connect the account's other domains. Revoke a saved key any time under Settings → Connected provider accounts. 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 — a status-based gate, answered from a single indexed lookup 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.