Elixir client for the Domain Connect protocol.
Domain Connect lets a Service Provider (you) set up the DNS a custom domain needs to point at your app without the domain owner having to understand CNAME/TXT records. The owner clicks "connect", consents at their own DNS provider, and the records are applied. ~20 providers implement it, including GoDaddy, IONOS, Cloudflare, Squarespace Domains, WordPress.com, and Plesk.
This library covers the Service Provider side of the synchronous flow:
# 1. Discover whether (and how) the domain's DNS provider supports it.
{:ok, config} = DomainConnect.discover("rent.theirplace.com")
# 2. Build the URL to send the owner to. They click "apply" at their
# provider, the records land, your custom domain goes live.
{:ok, url} =
DomainConnect.apply_url(config,
provider_id: "exampleservice.com",
service_id: "rentals",
params: %{"target" => "portal.unitops.app"}
)provider_id/service_id identify your Domain Connect template (the
record set you register with each DNS provider), not the DNS provider. See
apply_url/2.
The asynchronous OAuth flow (programmatic record writes) is supported via
async_consent_url/2, async_token/2, async_apply/3, and async_refresh/2
(see DomainConnect.Async). Signed templates are supported on both flows by
passing :private_key + :key_id (see apply_url/2 and DomainConnect.Signing).
Summary
Functions
Builds the synchronous apply URL to redirect the domain owner to.
Asynchronous flow: apply a template using a token.
See DomainConnect.Async.apply/3.
Asynchronous (OAuth) flow: build the consent URL to redirect the owner to.
See DomainConnect.Async.consent_url/2.
Asynchronous flow: refresh an access token. See DomainConnect.Async.refresh/2.
Asynchronous flow: exchange an authorization code for a token.
See DomainConnect.Async.get_token/2.
Discovers the Domain Connect configuration for domain. See
DomainConnect.Discovery.discover/2 for options.
Convenience: true if domain's provider supports the synchronous apply flow.
Splits a domain into its registrable zone and sub-host via the Public Suffix List, without any DNS lookup.
Functions
@spec apply_url( DomainConnect.Config.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Builds the synchronous apply URL to redirect the domain owner to.
On arrival the DNS provider shows a consent screen for your template and, on approval, writes the records. Resolves to:
<url_sync_ux>/v2/domainTemplates/providers/<provider_id>/services/<service_id>/apply?...Required options
:provider_id— your template'sproviderId(e.g."exampleservice.com").:service_id— your template'sserviceId(e.g."hosting").
Optional options
:params— a map of template variable values (string keys), merged into the query (e.g.%{"target" => "portal.unitops.app", "ttl" => "3600"}). Must not contain the reserved keysdomain,host,redirect_uri,state, orgroupId.:redirect_uri— where the provider returns the owner after applying.:state— opaque value echoed back to:redirect_uri.:group_ids— record group ids for partial templates; a list of strings or a comma-separated string.
Returns {:error, :sync_not_supported} if the provider advertised no sync UX,
{:error, {:missing, key}} if a required id is absent, or
{:error, {:reserved_params, keys}} if :params collides with a reserved key.
Signed templates
For templates that require a signed request, pass :private_key (an
unencrypted RSA private-key PEM) and :key_id (the TXT record name where the
matching public key is published). The query is signed (RSA-SHA256) and sig
/key are appended. Without both, an unsigned URL is built.
@spec async_apply(DomainConnect.Config.t(), DomainConnect.Token.t(), keyword()) :: :ok | {:error, term()}
Asynchronous flow: apply a template using a token.
See DomainConnect.Async.apply/3.
@spec async_consent_url( DomainConnect.Config.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Asynchronous (OAuth) flow: build the consent URL to redirect the owner to.
See DomainConnect.Async.consent_url/2.
@spec async_refresh( DomainConnect.Config.t(), keyword() ) :: {:ok, DomainConnect.Token.t()} | {:error, term()}
Asynchronous flow: refresh an access token. See DomainConnect.Async.refresh/2.
@spec async_token( DomainConnect.Config.t(), keyword() ) :: {:ok, DomainConnect.Token.t()} | {:error, term()}
Asynchronous flow: exchange an authorization code for a token.
See DomainConnect.Async.get_token/2.
@spec discover( String.t(), keyword() ) :: {:ok, DomainConnect.Config.t()} | {:error, :not_supported | term()}
Discovers the Domain Connect configuration for domain. See
DomainConnect.Discovery.discover/2 for options.
Convenience: true if domain's provider supports the synchronous apply flow.
Swallows discovery errors into false. Accepts the same options as
discover/2.
Splits a domain into its registrable zone and sub-host via the Public Suffix List, without any DNS lookup.
iex> DomainConnect.zone_and_host("rent.theirplace.co.uk")
{:ok, "theirplace.co.uk", "rent"}Useful for showing the owner exactly what you'll configure ("we'll set up
rent on theirplace.co.uk") before discovery.