Which path are you on?
CustomDomain™ has four front doors: the console, the API, the embeddable widget and the MCP server. Pick yours, then read the truths every path shares.
CustomDomain™ is one product with four front doors. The model underneath is
the same everywhere: a workspace has applications; an application owns
connections (and its origin, keys and webhooks); a connection is one domain
moving pending → propagating → live; live means the edge terminates TLS
and proxies the domain to the application's origin.
| You are… | Use | Start |
|---|---|---|
| Pointing your own domain at your app | the console | Console (no code) |
| A product that provisions domains for customers server-to-server | the API (/v1, cd_ API key) | API |
| A product whose customers connect their domains inside your UI | the widget (customdomain-js) | Widget |
| An AI agent or MCP client | the MCP server (mcp.customdomain.ai) | MCP |
| Building one of the above before you have a customer's domain | test mode, made up .test names, no card | Test mode |
Shared truths
These hold on every path. If a screen or a response seems to contradict one of them, that is a bug. Tell us.
- Four statuses.
pending(records not yet seen),propagating(a rail wrote them, we are watching DNS),live,failed. pendinghas no deadline. Checks run every five seconds at first and back off to every 15 minutes; after 72 hours the connection stayspendingwitherror_code: setup_incompleteand is re-checked every six hours, forever. It goes live the moment the records appear.- Records written for the user usually verify in seconds. A rail's write is checked at once, and when public resolvers still hold an old answer we ask the zone's own nameservers.
propagatingfails after 24 hours (error_code: propagation_timeout): records were written but never resolved. Usually a wrong value or a Cloudflare proxy (orange cloud) in the way.failedis not terminal. Resume /:recheckrevives it; re-creating the same domain revives it (200); real traffic at the edge revives it.- The diagnosis is on the connection:
error_codeanderror_messageonGET /v1/connections/{id}, on every list, and on the webhook events. - Test mode runs all of this on made up names. A test mode
application connects
.testnames, goeslive, stayspendingorfailedon a short simulated clock chosen by the name, sends the same webhooks marked"sandbox": true, and counts for nothing: no free domain, no allowance, no bill. - HTTPS only after
live. The certificate issues on the first request once the connection is live; a domain pointed at us before that gets a clear "not active yet" page, never a raw error. - Root domains work everywhere, but the record differs by provider
(ALIAS/flattened CNAME vs
Arecords); see Root domains. - "Automatic" means an adapter exists, not "the user types nothing". Read
rails/recommended_rail/end_user_automaticon the pre-flight (see Rail truth). - Drift monitoring watches live connections hourly; alerts are not delivered on the hosted service yet.
Which credential do I have?
| You got it from | It is | Use it for |
|---|---|---|
| The console → API & SDK | a cd_live_… / cd_test_… API key (tenant-scoped; keys created earlier start sk_ and still work). Every workspace has a default key from the start, which owners and admins can view at the top of the page at any time. Another key from Create key is shown once and needs a card on file (14 day free trial) | server-to-server API calls, the MCP server, and minting widget tokens through POST https://app.customdomain.ai/api/widget-token |
POST /v1/applications (an application created over the API) | the application's client secret (sk_…, shown once in that response). A console signup never shows one | minting widget tokens from your server with POST /v1/tokens |
| A widget token endpoint (above) | a short-lived widget JWT (one application, optionally one domain, optionally one end user) | the browser: customdomain-js open({ token }), or direct /v1 calls from the page |
| An agent grant | a delegated agent token | the agent's scoped API calls |
Most customers sign up in the console, so they hold an API key and mint widget
tokens through /api/widget-token; the client secret path is for applications
created over the API. An API key is not a client secret and the two are not
interchangeable. See Widget tokens.
A widget JWT can never mint another token, never read another application's data, and never choose the records we write. Anything that chooses records requires an API key.
Introduction
CustomDomain™ is custom-domain onboarding for multi-tenant SaaS: DNS setup, automatic SSL and a reverse proxy. Learn the connect, secure, serve, monitor loop.
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.