Domain already added to another Microsoft 365 organisation: find the tenant and release it.
The lookup came back with my own name as the organisation. I was trying to add a business domain to my Microsoft 365 tenant, verification kept failing because another tenant already held the domain, and a free public lookup showed that the other tenant was one I had set up myself in 2018 and long since forgotten about. This is how I found that tenant, released the domain from it with Microsoft Graph PowerShell, ran into a forceDelete that was accepted without any error and still failed, and verified the domain in my own tenant without touching DNS.
The error, and why it does not tell you where the domain is
A custom domain in Microsoft Entra ID can be verified in only one tenant at a time. When I added the domain to my tenant, verification failed and the admin centre reported that the domain was already in use by another organisation. It showed an administrator account for that organisation with most of its characters hidden, and nothing else that helped: no tenant name, no tenant ID, and no way to tell whether the other tenant belonged to a customer, a former provider, or me. Some versions of the message name the other organisation's initial onmicrosoft.com domain, while mine showed only the masked administrator. Even when that domain is shown, you still do not have the tenant ID, the cloud, or whether the tenant is managed.
That is the real difficulty with this error. It is about verification failing in your own tenant, so it tells you little about where the domain actually is, and the holding tenant is not visible to the person who needs the domain. The information that answers the question is public and takes seconds to retrieve, but nothing on that screen tells you it exists.
Finding the holding tenant with home realm discovery
Two public Microsoft endpoints tell you almost everything about where a domain lives, and neither needs a sign-in or a module. The first is the user realm lookup, which you can call with any address on the domain, because the address itself does not need to exist:
Invoke-RestMethod "https://login.microsoftonline.com/[email protected]&json=1" State : 4
UserState : 1
Login : [email protected]
NameSpaceType : Managed
DomainName : yourdomain.com
FederationBrandName : Contoso Ltd
CloudInstanceName : microsoftonline.com
CloudInstanceIssuerUri : urn:federation:MicrosoftOnline A NameSpaceType of Managed or Federated means a tenant has claimed the domain, and FederationBrandName is the organisation name of that tenant. In the sanitised capture above it reads Contoso Ltd. When I ran it against my domain, it read my own name, and that one field told me more than the error ever had: the tenant holding the domain was one I had created myself in 2018 and then forgotten. Microsoft does not formally document getuserrealm.srf, so I read its fields as observed behaviour that can change, not as a contract.
The second endpoint is the tenant's OpenID configuration document, which you can request by domain name. Its issuer URL contains the tenant ID, and it also tells you which cloud the tenant lives in:
$cfg = Invoke-RestMethod "https://login.microsoftonline.com/yourdomain.com/v2.0/.well-known/openid-configuration"
$cfg.issuer # contains the holding tenant GUID
$cfg.cloud_instance_name # microsoftonline.com for the worldwide commercial cloud
$cfg.tenant_region_scope # for example NA, EU, AS https://login.microsoftonline.com/11111111-1111-1111-1111-111111111111/v2.0
microsoftonline.com
NA The GUID between the host name and /v2.0 is the holding tenant ID, and it is what you connect to later. If you run the same two lookups against a domain you know is in your own tenant and the GUIDs differ, the domain sits in a separate directory and you have found the tenant holding it.
I later turned these lookups into a free web tool, the Domain Tenant Lookup, which works in your browser with no PowerShell and no sign-in, and can also compare two domains to tell you whether they share a tenant. If you prefer to stay in PowerShell, Get-DomainTenant.ps1 in the GitHub repository runs both lookups in one step.
Managed or unmanaged, and why it decides your route
Before changing anything, it is worth knowing what kind of tenant holds the domain, because the answer decides which route is open to you. An unmanaged tenant is one that Microsoft created automatically when someone signed up for a service with an address on the domain, and no administrator was ever appointed. Because nobody runs it, Microsoft lets the owner of the domain take it over from their own tenant through external admin takeover, which is a domain verify call with forceTakeover set to true:
Connect-MgGraph -TenantId <DESTINATION_TENANT_ID> -Scopes "Domain.ReadWrite.All" -ContextScope Process
$body = @{ forceTakeover = $true } | ConvertTo-Json
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/v1.0/domains/yourdomain.com/verify" `
-Body $body `
-ContentType "application/json" A managed tenant has a real administrator, and external takeover does not move a domain out of it. The Managed value in the realm lookup does not settle this, because it describes how sign-in is handled rather than whether anyone administers the tenant, but two other signals do, and I saw both. The admin centre had already shown a masked administrator for the other organisation, which an unmanaged tenant would not have. The forceTakeover call, made with the expected TXT record live in DNS, came back with HTTP 400, the code Request_BadRequest and this message:
Invalid cross-cloud verification code Both tenants were in the same worldwide commercial cloud, so the wording is misleading. Microsoft does not document this error, and I saw it against a managed tenant, so I treat it as one more sign of a managed tenant rather than as evidence of a cloud mismatch. My forgotten tenant was managed, and it was mine, so the route was to release the domain from inside it as its administrator.
Releasing the domain with Microsoft Graph PowerShell
The order of the next steps matters, so I have numbered them. They assume you administer the holding tenant: Domain Name Administrator is the least privileged role for removing a domain, deleting or renaming a user needs at least User Administrator, and the Global Administrator of a tenant you created yourself covers all of it.
Step 1: connect to the holding tenant and confirm it
Use the tenant ID from the OpenID lookup, and sign in with an account whose user name is not on the domain you are removing, because Microsoft notes that you cannot delete a domain your own sign-in name uses. I connect with -ContextScope Process so that the sign-in lasts for this PowerShell session only, since Microsoft Graph PowerShell otherwise caches the sign-in across sessions, and a cached session pointed at the wrong tenant is the most expensive mistake available here.
Connect-MgGraph -TenantId <HOLDING_TENANT_ID> -Scopes "Domain.ReadWrite.All","Directory.Read.All" -ContextScope Process
Get-MgContext | Select-Object TenantId, Account | Format-List The TenantId must match the holding tenant ID exactly before you go any further.
Step 2: list what still uses the domain
The domain must not be the default domain of the tenant, and Microsoft documents that a domain cannot be deleted while any object still depends on it, so the next job is to find out what those objects are:
Get-MgDomain | Select-Object Id, IsVerified, IsDefault, IsInitial | Format-Table -AutoSize
$refs = Get-MgDomainNameReference -DomainId "yourdomain.com" -All
"References found: $(($refs | Measure-Object).Count)"
$refs | ForEach-Object {
[PSCustomObject]@{
Id = $_.Id
Type = $_.AdditionalProperties.'@odata.type'
Name = $_.AdditionalProperties.displayName
UPN = $_.AdditionalProperties.userPrincipalName
}
} | Format-Table -AutoSize References found: 1
Id Type Name UPN
-- ---- ---- ---
33333333-3333-3333-3333-333333333333 #microsoft.graph.user Old Account [email protected] Mine had exactly one blocker: an account from 2018 with no licence, still enabled, that still had an email address on the domain as its primary SMTP proxy address. Keep the Format-Table at the end of each command, because the first time I ran this block without it, objects of different shapes printed as blank lines. If a subdomain of the domain is verified in the same tenant, remove that first as well, since Microsoft lists it as a removal error.
The part that surprised me: forceDelete reported no error
With that reference list in front of me, I tried forceDelete next, the Graph operation that renames everything still using a domain to the initial onmicrosoft.com domain and then deletes the domain. I set disableUserAccounts to false, because it defaults to true and disables every account it renames:
$body = @{ disableUserAccounts = $false } | ConvertTo-Json
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/domains/yourdomain.com/forceDelete" -Body $body -ContentType "application/json" The call was accepted without any error. My checks of the tenant list and the realm lookup over the next few minutes showed nothing wrong either, because neither of them reads the domain state. The failure was first seen when I read that state a few minutes later, with the reason sitting inside the status string itself:
$state = (Get-MgDomain -DomainId "yourdomain.com").State
$state | Select-Object Operation, Status | Format-List Operation : ForceDelete
Status : Failed. Reason = Cannot delete the domain currently referenced by at least 1 objects. Conflicting object User/33333333-3333-3333-3333-333333333333. Because the reason is appended to the status, I test it with -like 'Failed*' rather than -eq 'Failed', which would have missed it. And because none of my earlier checks read the state, I know when I first saw the failure, not when the operation actually failed.
The error named the conflicting user and said nothing about Exchange. Linking it to Exchange is my reading, not something the error states: the user's primary SMTP address was still on the domain, and Microsoft states that forceDelete needs any references where Exchange is the provisioning service to be updated or removed first. A failed forceDelete can also stay recorded in the domain state, so after clearing the blocker in the next steps I watched for the domain to disappear from Get-MgDomain rather than reading the state again.
Step 3: delete the stale account, recycle bin included
That left the account for me to deal with directly. Renaming it off the domain is the non-destructive route, and the repository describes it, but I did not test it. My account was stale, carried nothing I needed and had no licence, so I deleted it. Deleting a user needs more than the scopes used so far, so I disconnected, reconnected with the user scopes added, and checked the tenant again:
Disconnect-MgGraph
Connect-MgGraph -TenantId <HOLDING_TENANT_ID> -Scopes "Domain.ReadWrite.All","Directory.Read.All","User.ReadWrite.All","User.DeleteRestore.All" -ContextScope Process
Get-MgContext | Select-Object TenantId, Account | Format-List A single Remove-MgUser moves the account to the recycle bin, where Microsoft keeps it with all of its properties for 30 days so that it can be restored, and while it sits there it can still block the domain. I deleted it from the recycle bin straight away, so I never tested whether it would have blocked, but the second delete settles the question either way:
Remove-MgUser -UserId <OBJECT_ID>
Start-Sleep -Seconds 30
Remove-MgDirectoryDeletedItem -DirectoryObjectId <OBJECT_ID> Step 4: remove the domain and watch it leave
With the account removed properly, recycle bin included, the reference count was zero and the normal removal went through:
$count = (Get-MgDomainNameReference -DomainId "yourdomain.com" -All | Measure-Object).Count
"References found: $count"
if ($count -eq 0) { Remove-MgDomain -DomainId "yourdomain.com" } Within about a minute the domain had left the old tenant and the realm lookup returned Unknown. This is what the script reports for a domain that no tenant claims:
.\scripts\Get-DomainTenant.ps1 -Domain yourdomain.com Domain : yourdomain.com
NameSpaceType : Unknown
FederationBrand :
AuthURL :
TenantId :
CloudInstance :
RegionScope :
No tenant claims this domain. It is free to verify. Step 5: verify the domain in your own tenant
Once the realm lookup returns Unknown and the OpenID lookup returns no tenant, the domain is free. After working in the holding tenant it is easy to still be connected to it, so I disconnected, connected to my own tenant and confirmed the tenant ID before verifying:
Disconnect-MgGraph
Connect-MgGraph -TenantId <DESTINATION_TENANT_ID> -Scopes "Domain.ReadWrite.All" -ContextScope Process
Get-MgContext | Select-Object TenantId, Account | Format-List
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/domains/yourdomain.com/verify"
Get-MgDomain -DomainId "yourdomain.com" | Select-Object Id, IsVerified, IsDefault | Format-Table -AutoSize Id IsVerified IsDefault
-- ---------- ---------
yourdomain.com True False Verification succeeded a few minutes after the domain left the old tenant, with no DNS change at all. The verification TXT record for my tenant was already live in DNS from my earlier attempts, and it was still valid, so there was nothing to add. Nothing in the whole release touched MX, SPF or any other record, so the mail still running on the domain carried on without interruption. In my case the mail was not hosted in the holding tenant. If the holding tenant's Exchange Online is still receiving mail for a domain, that mail stops when the domain is removed, so move it first.
A caveat for domains held in a national cloud
In testing, I found one case where the realm lookup gives the wrong impression. A domain held by a tenant in the US Government cloud returned a NameSpaceType of Unknown from the worldwide realm endpoint, which on its own reads as free to verify, while its OpenID configuration still named the tenant, with a cloud_instance_name of microsoftonline.us. This is observed behaviour, not documented by Microsoft, so I read the two lookups together. Unknown with no tenant ID means the domain is free to verify, Unknown with a tenant ID in another cloud means the domain is held in that cloud and is not free to verify, and Unknown with a tenant ID in the worldwide cloud, or with no cloud value, means the two lookups disagree and the result is inconclusive. Both the web tool and Get-DomainTenant.ps1 now follow that rule.
What I took away from it
The first lesson is that the answer was public the whole time. A lookup that takes seconds told me more than the error message ever did, and home realm discovery is the first thing I would run for any domain already in use problem.
The second lesson is that old tenants do not go away on their own. A trial or a lab you set up years ago can keep a domain verified long after you stop thinking about it, and in my case a single forgotten account from 2018, with no licence and one email address on the domain, was enough to hold the whole domain in place.
The third is that an accepted request is not a finished one. forceDelete returned no error and still failed, and I only found out by reading the domain state, so I now read the domain state after a forceDelete rather than trusting the response, and when I remove a user to free a domain I delete it from the recycle bin as well.
Finally, I check which tenant I am in before every change. Connecting with -ContextScope Process and reading Get-MgContext costs a few seconds, and it is the cheapest protection there is against deleting something in the wrong tenant.
Microsoft references
- Add and verify custom domain names in Microsoft Entra ID
- Admin takeover of an unmanaged directory
- Delete domain (Microsoft Graph)
- domain: forceDelete (Microsoft Graph)
- domainState resource type (Microsoft Graph)
- You get an error message when you try to remove a domain from Microsoft 365
- Delete a user (Microsoft Graph)
- Restore or permanently remove a recently deleted user
- Permanently delete item (directory object) (Microsoft Graph)
- Microsoft Graph PowerShell authentication commands
error messages and what they mean
Domain already added to another Microsoft 365 organisation
Another tenant has verified the domain, and a domain can be verified in only one tenant at a time. The exact message varies by admin centre and language, and it may not say which tenant holds the domain, as in my case. Run the realm lookup and the OpenID lookup, or the Domain Tenant Lookup, to get the organisation name, the tenant ID and the cloud, then decide whether the tenant is managed or unmanaged.
Failed. Reason = Cannot delete the domain currently referenced by at least 1 objects. Conflicting object User/<OBJECT_ID>.
forceDelete was accepted but could not finish, because an object still references the domain. The object ID after User/ is the account to deal with. In my case it was an unlicensed account whose primary SMTP address was on the domain, and my reading is that the Exchange precondition is what forceDelete hit. Rename the account off the domain, or delete it from the recycle bin as well if it is stale, then remove the domain.
Invalid cross-cloud verification code
Returned as HTTP 400 with Request_BadRequest when I tried external admin takeover with forceTakeover set to true, with the TXT record live in DNS and both tenants in the same cloud. Microsoft does not document this error. I saw it against a managed tenant, where takeover is not available, so release the domain from inside that tenant instead.
Domain has associated subdomains
Microsoft lists this as an error when removing a domain. A subdomain of the domain is still verified in the same tenant, so remove the subdomain first, then remove the parent domain.
The scripts, the five step-by-step docs and the sanitised logs from the real run are in the GitHub repository, and the lookup itself is free to use in your browser.