A verified domain (for example ecole.fr) proves that the organization owns the addresses on that domain. It makes the accounts with those addresses “managed”, and enables automatic joining, required single sign-on and account creation by the SCIM directory. An organization can have up to 50 domains.
All routes are under https://. Scopes: security:read to read, security:write to write.
The domain objectLink to the The domain object section
{
"id": "3c1f6a2e-5b8d-4e7a-9f10-2b6c8d4e0a11",
"domain": "ecole.fr",
"status": "pending",
"verified_at": null,
"last_checked_at": null,
"last_error": null,
"auto_join": false,
"default_role_key": null,
"default_group_ids": [],
"created_at": 1762339200,
"updated_at": 1762339200,
"txt": { "type": "TXT", "name": "_moonai.ecole.fr", "value": "moonai-verification=4f9c2a7b1e6d3c8a0b5f2e9d7c1a4b6e" }
}| Field | Description |
|---|---|
status | pending, verified or failed. |
last_error | record_not_found, dns_unavailable, domain_taken, or lost:<n> after failures of the weekly check. |
txt | The record to publish: its name and value never change. |
auto_join | Auto-join for accounts on this domain. |
default_role_key, default_group_ids | Role and groups given to accounts that join through this domain. |
List domainsLink to the List domains section
GET /domains · security:read
Returns items, total, verified_count and paid_subscription (the workspace is paid by card, so automatic joining is not available).
Add a domainLink to the Add a domain section
POST / · security:write
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | yes | The domain name, without @ or https://. Internationalized names are accepted. |
Returns the domain, pending verification.
| Status | code | Cause |
|---|---|---|
| 400 | domain_invalid | Invalid domain name. |
| 400 | domain_public | Domain of a public or disposable email service. |
| 400 | invalid_request | More than 50 domains. |
| 409 | domain_exists | The domain is already in the list. |
Verify a domainLink to the Verify a domain section
- Publish the
TXTrecord given bytxtin the domain’s DNS zone: name_moonai.<domain>, valuemoonai-verification=.<token> - Call
POST /(domains/ {domain_id}/ verify security:write).
The response is the domain with check: {"found": , or found: false with error (record_not_found or dns_unavailable). It returns 200 even when the record is not visible yet: call it again once the record is published.
curl -X POST https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/domains/$DOMAIN_ID/verify \
-H "Authorization: Bearer $MOONAI_ADMIN_KEY"import os
import requests
BASE = f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}"
HEADERS = {"Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}"}
r = requests.post(f"{BASE}/domains/{os.environ['DOMAIN_ID']}/verify", headers=HEADERS, timeout=30)
r.raise_for_status()
domain = r.json()
print(domain["status"], domain["check"])const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const r = await fetch(`${BASE}/domains/${process.env.DOMAIN_ID}/verify`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}` },
});
if (!r.ok) throw new Error(await r.text());
const domain = await r.json();
console.log(domain.status, domain.check);| Status | code | Cause |
|---|---|---|
| 409 | domain_taken | The domain is already verified by another organization. |
| 429 | rate_limited | More than 20 checks per organization within 10 minutes. |
A verified domain is checked again every 7 days. After three failures in a row, it goes back to pending and automatic joining is turned off; the owner is notified. A DNS server that does not respond does not count as a failure.
Configure a domainLink to the Configure a domain section
PATCH / · security:write
| Field | Type | Required | Description |
|---|---|---|---|
auto_join | boolean | no | A person whose verified address is on this domain joins the organization when they sign in, as long as seats are left. |
default_role_key | string | no | manager, billing or custom:<id>; never admin. null clears it. |
default_group_ids | list | no | Up to 50 static groups; not dynamic, not from the directory, and not granting the admin role. |
| Status | code | Cause |
|---|---|---|
| 403 | role_escalation | The role or groups grant more than the key’s permissions. |
| 404 | not_found | A group was not found (message Groupe ). |
| 409 | domain_not_verified | auto_join on a domain that is not verified. |
| 409 | auto_join_paid_subscription | auto_join on a workspace paid by card: each account that joins would add a billed seat. Invite the accounts instead. |
| 409 | directory_group_admin | A group that grants the admin role. |
Remove a domainLink to the Remove a domain section
DELETE / · security:write
Response: {"success": . Accounts on this domain are no longer managed by the organization on that basis.
Invite the domain’s existing accountsLink to the Invite the domain’s existing accounts section
GET / · security:read
Lists the existing Moon AI accounts whose address is on this verified domain, with status: active or suspended (already members), invited (invitation pending) or none. Parameters: page, limit.
POST / · security:write
| Field | Type | Required | Description |
|---|---|---|---|
user_ids | list | yes | 1 to 500 accounts on the domain. |
Sends a member invitation to each one. Response:
{
"invited": ["41ab2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],
"skipped": [{ "user_id": "77cd8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f", "reason": "already_member" }],
"domain": "ecole.fr"
}Reasons in skipped: not_in_domain, already_member, seats_full, invite_limit (daily invitation limit), error. Both routes return 409 on a domain that is not verified.