CustomDomain™ docs
Getting started

API, provision domains from your server

Provision custom domains from your server with an API key: pre-flight check, create, apply records, verify and react, plus mistakes integrators actually make.

Use the API when your server decides which domains get connected: a platform that provisions a custom domain per customer, a migration script, an internal tool. The browser-embedded path is the widget.

Base URL for the hosted service: https://api.customdomain.ai. Auth: Authorization: Bearer cd_live_…. Every workspace has a default key, which owners and admins can view at the top of the console's API & SDK page with no card on file. To make another, press Create key there (admins and owners; a card on file starts the 14 day free trial, and without one the request is refused with 402 billing_required). Keys created before the switch to cd_ start with sk_ and keep working.

1. Pre-flight, and read what it says

curl -X POST https://api.customdomain.ai/v1/domains:check \
  -H "Authorization: Bearer cd_live_..." -H "Content-Type: application/json" \
  -d '{"domain":"shop.acme.com"}'

Branch on rails and recommended_rail, not on setup_type: automatic only means an adapter exists (for GoDaddy that is a Personal Access Token the user has to create at GoDaddy). end_user_automatic: true is the only field that means "nobody has to type anything". A blocked_reason (unregistered, platform_subdomain) means stop and tell the user why. dashboard_url is where to send them for manual records. If you supply your own records, pass them here too so conflicts are computed against the real set.

2. Create

curl -X POST https://api.customdomain.ai/v1/connections \
  -H "Authorization: Bearer cd_live_..." -H "Content-Type: application/json" \
  -d '{"domain":"shop.acme.com","application_url":"https://tenant-42.yourapp.com"}'

application_url makes the CustomDomain™ edge serve the domain from that origin, which needs the Growth plan or above (402 plan_upgrade_required otherwise). Leave it out when your own servers serve the domain.

201 with the connection and its desired records. Re-posting the same domain returns the existing connection with 200; if the only existing one is failed, it is revived and returned. Idempotent by design: call it on every "connect" click.

For records only you know (SES DKIM, verification TXT), follow with PUT /v1/connections/{id}/records (see Bring your own records).

3. Apply (optional)

If you hold a provider credential for the zone, write the records yourself:

curl -X POST https://api.customdomain.ai/v1/connections/<ID>/apply \
  -H "Authorization: Bearer cd_live_..." -H "Content-Type: application/json" \
  -d '{"provider":"cloudflare","credential":"<scoped token>"}'

A rejected credential is 422 ProviderAuthenticationError; any other provider refusal is 422 dns_write_failed with the provider's reason. Route 53 temporary (ASIA…) keys also need the session token. Otherwise, show the records from GET /v1/connections/{id}/records and let the user add them.

4. Verify and react

Subscribe to webhooks (connection.live, connection.failed; both carry error_code / error_message when there is one) and poll GET /v1/connections/{id} as the fallback. pending never fails on its own; propagating fails after 24 hours; POST /v1/connections/{id}:recheck revives a failed connection.

Mistakes we see

  • Promising "automatic" from setup_type. Use rails.
  • Telling users "unregistered" on a resolver hiccup. Only act on registered === false when the field is present; a transient failure omits it and sets detection_error: resolver.
  • Sending your API key from a browser. Mint a widget JWT server-side and use that in the page. With an API key that is POST https://app.customdomain.ai/api/widget-token; see Widget tokens.
  • Putting an API key in client_secret. POST /v1/tokens takes the application's client secret, a different credential, and answers 401 to anything else.
  • Treating connection.applied as verified. Applied means written; only live means resolving and served.
  • Inventing the edge CNAME for an email-only connection. A connection whose records have no edge record is never served and gets no certificate, by design.

On this page