CustomDomain Docs

Removing a domain

Remove a connected domain and choose what happens to the DNS records CustomDomain™ added: take them out and restore what they replaced, keep them, or go manual.

Removing a domain always stops serving it, stops its certificate from renewing and stops monitoring it. What happens to the DNS records CustomDomain™ added is a choice: remove them (and put back what each one replaced) or keep them.

1. Read the plan

GET /v1/connections/{id}/removal says what removal can do, and changes nothing:

curl https://api.customdomain.ai/v1/connections/<ID>/removal \
  -H "Authorization: Bearer <WIDGET_JWT or API key>"
{
  "domain": "shop.acme.com",
  "connected_via": "sign_in",
  "access": "one_time",
  "record_removal": "reauthorize",
  "default_choice": "keep",
  "records": [
    { "type": "CNAME", "name": "shop", "value": "edge.customdomain.ai",
      "change": "replaced", "prior": [ { "type": "CNAME", "value": "shops.oldhost.example" } ],
      "action": "restore" }
  ],
  "reauthorize": { "methods": ["sign_in", "api_key"], "provider": "cloudflare", "flow": "oauth2" },
  "manual_steps": [ "At Cloudflare, change the CNAME record at \"shop\" from ... back to ..." ],
  "teardown": [ "The domain stops being served through CustomDomain™. ..." ]
}

Every write records what it did to each name (change: created, added, replaced, merged) and the values it replaced (prior), read from the provider or public DNS right before the write. Removal only touches values that are still ours: a record someone changed since is left and reported.

record_removal depends on the access CustomDomain™ still holds:

How it was connectedAccess heldrecord_removal
Managed DNS (the zone we host)continuousautomatic
Kept sign-in, continuous provider accountcontinuousautomatic
Kept Domain Connect authorizationcontinuousautomatic (partial when a value must be put back)
Route 53 access rolecontinuous, writes onlypartial (deletions by hand)
API key or sign-in that was not keptone timereauthorize: one more sign-in or key, or the manual steps
Domain Connect one-click, manual recordsnonemanual
Another application still serves the domainin_use: every record stays

2. Remove

# Remove the records (and put back what they replaced) where access allows
curl -X DELETE "https://api.customdomain.ai/v1/connections/<ID>?records=remove" \
  -H "Authorization: Bearer <WIDGET_JWT or API key>"

# Keep the records
curl -X DELETE "https://api.customdomain.ai/v1/connections/<ID>?records=keep" ...

With records the answer is 200 and says what happened to each record (removed, restored, left_in_place, failed, manual_steps). Without it the call keeps its original contract and answers 204: the records are taken out only through a kept Domain Connect authorization, a kept sign-in or a zone CustomDomain™ hosts, and kept otherwise (a connected provider account or the Route 53 access role never write on a DELETE without a choice).

A removal never touches a record whose value changed since it was written, a value that was already there before CustomDomain™ wrote it, or a record another connected domain still uses (a www record a root domain's redirect and a www connection share, a CAA record several subdomains rely on). A key or a sign-in must reach the zone that holds the records: when it shows none of them (it may belong to another account), the answer lists the manual steps instead of claiming anything was removed.

When removing the records needs access that was not kept, ?records=remove answers 409 reauthorization_required with the plan. Then either:

  • Sign in once more: POST /v1/connections/{id}/oauth:start with "purpose": "remove". The provider sign-in removes the records and the domain, and its result carries "purpose": "remove" and the outcome. The sign-in is used once and never kept.
  • Paste a key: POST /v1/connections/{id}:disconnect with { "records": "remove", "provider": "cloudflare", "credential": { "token": "..." } }. The key is used once and never stored.
  • Do it by hand: remove the domain with ?records=keep and follow manual_steps.

While a removal runs, a second one answers 409 removal_in_progress.

A delegated agent token can remove a domain too, when its grant carries domains:disconnect and the connection is in its application. It must send confirm=<domain>: without it the answer is 400 confirmation_required (its details name the domain), and with another domain 409 confirmation_mismatch. Nothing is removed in either case.

Any other credential (an API key, an application's client credentials token, a widget token) may leave confirm out, and the removal keeps its original contract. One that sends it is held to it the same way: another domain is 409 confirmation_mismatch, an empty value is 400 confirmation_required, and nothing is removed. Case, a scheme, a path and a trailing dot do not matter. The MCP server's disconnect-domain always sends it, whatever the credential.

What always happens

  • The edge stops approving the host at once and every edge forgets it within a minute (GET /internal/removed-hosts), so the certificate stops renewing and is deleted from the edge cache.
  • Monitoring stops.
  • connection.disconnected fires with a removal object: the choice, the outcome, the records removed, restored and left, and any manual steps. See the event catalog.
  • GET /v1/connections/{id}/removal keeps answering once the connection is gone: removed: true and result, what the removal did with the records. Read it, not the connection's 404, to learn the outcome.

From the widget

customdomain.removeDomain({ applicationId, token, connectionId }) (or prefilledDomain in place of connectionId) opens the same flow for your end user: the records, the choice, one more sign-in when needed, and a summary with anything left to delete by hand, in their language. The outcome arrives in onRemoved and the customdomain:removed event. See the SDK reference.

Mint the widget token on your server for that end user and that domain: POST /v1/tokens with domain and end_user_ref (your own id for the end user). An end user's token that is not bound to the domain, or bound to another end user than the one the connection names, cannot remove it. A connection created with a token that carries end_user_ref is stamped with it.

Finding the connection

An app that stored the domain but not the id can call GET /v1/connections:lookup?domain=shop.acme.com.

On this page