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. Userails. - Telling users "unregistered" on a resolver hiccup. Only act on
registered === falsewhen the field is present; a transient failure omits it and setsdetection_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/tokenstakes the application's client secret, a different credential, and answers401to anything else. - Treating
connection.appliedas verified. Applied means written; onlylivemeans 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.
Console, connect your own domain
Connect your own domain with no code: sign up, tell us who the domains are for, add the DNS records, verify, and see the domain serving your app over HTTPS.
Widget, let your customers connect their domains
Let your customers connect domains inside your product: mint a widget token on your server, open the modal, and see what customers get per DNS provider.