Connections

Create a domain connection (idempotent per app + domain)

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 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.

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.

post/connections

Request

  • Base URL: https://api.customdomain.ai/v1
  • URL: https://api.customdomain.ai/v1/connections
  • Auth: one of:
    • HTTP bearer
    • HTTP bearer
    • OAuth 2 (authorization code, authorization URL https://api.customdomain.ai/oauth/agent/authorize, token URL https://api.customdomain.ai/oauth/agent/token, scopes: domains:connect)

Request body

domainstring required
application_urlstring uri

Origin the edge reverse-proxies this connection's traffic to (Power). Validated on the way in: it must be an absolute http(s) URL with a public host; private, loopback and cloud-metadata targets are rejected with a 400. Power is part of the Growth plan and up: on a plan without it the create is refused with 402 plan_upgrade_required and nothing is stored. Omit to leave the edge on its own router.

www_redirectboolean

Serve an apex↔www redirect for this connection.

override_spfboolean

Replace an existing SPF record instead of merging into it. Honoured only for providers whose connection check reports spf_override_support as true; for every other provider the existing record is merged, as when this is false.

validate_dmarcboolean

Accepted, stored with the connection and echoed back. It is not acted on yet: DMARC is not checked during verification today.

validate_caaboolean

Adds the two apex CAA records that authorize Let's Encrypt (issue and issuewild) to the connection's desired record set, skipping any already present. No separate CAA check is run against the domain's existing records. Edge routing only: a domain routed to your own infrastructure (a connect target) gets its certificate there, so no CAA records are added for it.

monitorboolean

Enrol the domain in drift monitoring: once the connection goes live the control plane registers a drift watch and the hourly sweeper re-checks the connection's DNS records, emitting domain.record_missing / domain.record_restored when they change. Unlike the four flags above this one DEFAULTS TO TRUE: omitting it enrols the domain. That is deliberate back-compat: the watch used to be registered unconditionally, so every connection created before this flag was honoured is monitored, and every existing integrator omits the field. Send false to opt a connection out; the value is echoed on the connection and is the only thing that decides whether the watch is registered. The plan's Monitor entitlement is not enforced today: monitoring is registered for every connection that has this on, regardless of plan.

batch_idstring

Caller-supplied id correlating every domain connected in one multi-domain session. Stamped on each connection and echoed back; empty for a standalone connect. Set once at create.

end_user_refstring

The integrator's OWN identifier for the end user this connect is attributed to, so the console can render "Connected by …" and search by customer. Empty ⇒ a console/direct connect. Set once at create and never mutated.

end_user_emailstring email

Optional. The email address of the end user this domain is connected for. After a Move they get one email with the new DNS records, sent on your behalf and naming your application (at most 3 per connection in 30 days). It wins over an address given when the token was minted, is stored only on the connection, and is shown back only in this call's response and on GET /connections/{id} with an API key.

application_idstring

API key callers only: create the connection on this application of the key's tenant. A widget or agent token may only name its own application (anything else is a 403).

Response

Existing connection replayed (idempotent)

idstring required
application_idstring required
domainstring required
hoststring

subdomain label; empty means the apex

provider_idstring

detected / applied DNS provider

setup_type'automatic' | 'manual' | 'async' | 'mcp' required

How a connection reaches live.

status'pending' | 'propagating' | 'live' | 'failed' required
managedboolean

true when the connection opted into managed mode: the control plane holds a durable, encrypted async grant and can re-apply (POST /v1/connections/{id}:reapply) or revert (DELETE /v1/connections/{id}) the template server-side without re-consent. Omitted (treated as false) for the default use-once connection.

created_atstring date-time required
sandboxboolean

true when the connection belongs to a test mode application. Its DNS is simulated, nothing real is read, written or served, and it counts for nothing (no free domain, quota, billing or metering). Copied from the application at create and never changed. Omitted (treated as false) on a real connection.

last_checked_atstring date-time

When the propagation poller last examined this connection. Omitted until the first check.

www_redirectboolean

Serve an apex↔www redirect. Echoed from create.

override_spfboolean

Replace an existing SPF record instead of merging into it, where the provider supports it. Echoed from create.

validate_dmarcboolean

Stored and echoed from create; not acted on yet.

validate_caaboolean

Whether the Let's Encrypt CAA records are part of the desired record set. Echoed from create.

monitorboolean

Whether the domain is enrolled in drift monitoring. Echoed from create, where it defaults to true when omitted, and honoured: the drift watch is registered when the connection goes live only if this is true. Connections created before the flag was honoured read true (they were monitored unconditionally, and were backfilled), so this is false only where a caller explicitly opted out.

batch_idstring

The multi-domain session this connection was created in. Omitted for a standalone connect.

end_user_refstring

The integrator's own identifier for the end user who connected this domain. Omitted for a console/direct connect.

retried_atstring date-time

When a human (or the create-path resume, or real edge traffic) last asked for this connection to be re-verified. The retry clock is max(created_at, retried_at): a revived connection gets a fresh window from here, not from its original creation. Absent when it was never retried.

records_source'integrator'

Present as integrator when the connection's desired record set was supplied through PUT /v1/connections/{id}/records rather than authored by us. Omitted for the usual server-synthesized or Domain Connect template-materialized set. Such a connection cannot use the Domain Connect rail, which applies our published templates at the provider.

created_via'' | 'widget' | 'console' | 'api' | 'agent' | 'mcp'

Where the connection was created, recorded once at create and never changed. widget: an end user's connect-scoped widget token (the embedded widget in your product). console: the CustomDomain™ console. api: an API key, or an app-scoped token, from your server. agent: a delegated agent access token. mcp: the MCP server. Empty for connections created before this was recorded. A trusted server-side caller may name its surface with the X-CustomDomain-Client request header (console or mcp); the header is a label only and grants nothing.

secure_root_domainboolean

The edge also issues/secures the registrable apex alongside this subdomain connection. Omitted (treated as false) when unset.

redirect_tostring uri

URL the edge 301s this host to (Power redirect target). Omitted when the connection is not a redirect.

power_root_path_accessstring[]

URL paths the edge may static-serve at the root (Power root-path access). Omitted when none are configured.

error_codestring

The last failure reason recorded for this connection, from whichever path failed, not only the background poller. It is persisted by: the propagation poller / :recheck verification (e.g. propagation_timeout, setup_incomplete, pending_expired, domain_already_connected); the foreground apply on POST /connections/{id}/apply (e.g. dns_write_failed, ProviderAuthenticationError, spf_merge_failed, apex_not_supported, forwarding_not_supported); the OAuth callback (which also records forwarding_not_supported); and the Domain Connect async callback (both e.g. provider_unavailable, ProviderAuthenticationError, apply_failed, internal_error, or the provider's own error parameter). Rejected request validation (400) is deliberately NOT recorded here: a malformed request is not a connection failure. Omitted when healthy or never failed; cleared when the connection next verifies.

error_messagestring

Human-readable detail for error_code. Populated on the same paths and cleared alongside it. Omitted when there is none.

write_rail'api_key' | 'sign_in' | 'domain_connect' | 'domain_connect_async' | 'route53_role' | 'provider_account' | 'managed_dns'

The rail that last wrote this connection's records. Omitted when no rail wrote them (a manual setup, or a connection from before this was recorded). It decides what removing the domain can do with the records (GET /connections/{id}/removal).

provider_account_idstring

The connected provider account whose credential wrote this connection's records and, when the account's access is continuous, keeps them (repairs and removal). Omitted when none.

removal_started_atstring date-time

Set while the domain is being removed: the records are being taken out and the connection is deleted when that finishes. The domain no longer serves from this moment. Omitted otherwise.

end_user_emailstring email

The end user's email address, when one was given and the feature is on. Present only on the response of the call that created the connection with it, and on GET /connections/{id} with an API key. Never in a list, a webhook, or any read by a widget token.

ask_end_user_emailboolean

Present (true) on a create response only when the widget should offer the end user an optional email field: the feature is on, the caller is a widget token, the domain is not a test domain and the connection has no address yet. The widget then calls PUT /connections/{id}/end-user-email. Omitted otherwise.

Example response

{
  "records": [
    {
      "type": "CNAME"
    }
  ]
}

Changes

No recorded changes to this operation across all 1 revision of this API.