Custom Domain
Connect flow

Apply records & go live

The three automatic rails, the manual fallback, propagation to live, and cert issuance.

Once a connection exists, get its desired records into the customer's DNS via one of the four rails. All of them move the connection pending → propagating → live.

Rail C — OAuth into the provider (flagship)

curl -X POST http://localhost:8080/v1/connections/<ID>/oauth:start \
  -H "Authorization: Bearer <WIDGET_JWT>" \
  -H "Content-Type: application/json" \
  -d '{"return_origin":"https://app.acme.example"}'
# → { "authorize_url": "https://<provider>/authorize?..." }

The widget opens authorize_url in a popup. The provider redirects the customer to GET /v1/connect/oauth/callback, which consumes the HMAC-signed single-use state, exchanges the code, writes the records with the one-time access token (discarded immediately, never stored), and returns an HTML page that postMessages { type: "customdomain:oauth", payload } to the vetted return_origin. The connection is now propagating.

Rail A — Domain Connect

curl -X POST http://localhost:8080/v1/connections/<ID>/domainconnect:start \
  -H "Authorization: Bearer <WIDGET_JWT>" \
  -H "Content-Type: application/json" \
  -d '{}'
# → { "apply_url": "https://<provider>/domainconnect/...", "service_id": "...", "dc_provider": "..." }

Redirect the customer to apply_url; their provider applies the signed template. Both the synchronous redirect and the asynchronous variant are supported. service_id may be omitted when a single/default template is loaded.

Rail B — API token (BYO)

The customer pastes a scoped provider token; it writes the records once and is never persisted:

curl -X POST http://localhost:8080/v1/connections/<ID>/apply \
  -H "Authorization: Bearer <WIDGET_JWT>" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "cloudflare",
    "zone": "customer.com",
    "credential": { "token": "<scoped-provider-token>" },
    "records": [ { "type": "CNAME", "host": "app", "value": "edge.customdomain.ai", "ttl": 3600 } ]
  }'

SPFM records are merged into any existing SPF TXT at write time (never clobbered); mark a record essential: true to fail the apply if it can't be written.

Manual

If no automatic rail is available, show the records from GET /v1/connections/{id}/records for the customer to add by hand. Nothing else is required — the poller watches for them.

Watch propagation → live

You don't poll the API for propagation — the control-plane's background poller (running automatically on a 1-minute interval) resolves each desired record against public DNS and, when they all match their intended values, flips the connection to live. On that transition the edge issues the TLS certificate on the next handshake and a connection.live webhook fires (exactly once). Read current state any time with GET /v1/connections/{id}.

A propagating connection whose records never appear within 24 hours becomes failed (and fires connection.failed). Manual pending connections don't time out — a customer may take days.

On this page