DNS API: Domains and contacts
This page describes the endpoints for domains and contacts (handles). Basics such as the base URL, authentication, and error format are described in the API overview. Domain endpoints require a token with access to the Domains area, contact endpoints access to the Contacts area.
The examples use the following variables:
export XCLOUD_DNS_TOKEN='xcdns_...'
export XCLOUD_DNS_API='https://app.xcloud.eu/api/dns/v1'
Contacts (handles)
Contacts hold the owner and contact data that the registry stores for a domain. A domain references contacts in four roles: owner, admin contact, technical contact, and zone manager (see Contacts in the user guide).
| Method | Path | Description |
|---|---|---|
GET |
/handles |
List all contacts |
GET |
/handles/{id} |
Show a contact |
POST |
/handles |
Create a contact |
PATCH |
/handles/{id} |
Change a contact |
DELETE |
/handles/{id} |
Delete a contact |
Contact attributes
| Attribute | Required | Description |
|---|---|---|
firstname |
yes | First name |
lastname |
yes | Last name |
company |
no | Organisation (company, association, or authority) |
street |
yes | Street and house number |
pcode |
yes | Postal code |
city |
yes | City |
country |
yes | Two-letter country code (ISO 3166-1), e.g. DE |
phone |
yes | Phone number in the format +49 421 000000 |
fax |
no | Fax number, same format as phone |
email |
yes | Email address |
Responses additionally contain:
| Attribute | Description |
|---|---|
id |
ID of the contact |
registrar_handle |
Reference of the contact at the registry |
in_use |
Whether a domain uses the contact |
created_at |
Creation time |
Phone and fax numbers must have the format +<country code> <area code> <number>,
separated by single spaces. The registry rejects other formats.
Fetch all contacts:
curl "$XCLOUD_DNS_API/handles" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN"
{
"data": [
{
"id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"registrar_handle": "MM12345-XCLOUD",
"firstname": "Max",
"lastname": "Muster",
"company": "Example GmbH",
"street": "Musterstr. 1",
"pcode": "28217",
"city": "Bremen",
"country": "DE",
"phone": "+49 421 000000",
"fax": "",
"email": "domains@example.com",
"in_use": true,
"created_at": "2026-09-14T10:12:48+02:00"
}
]
}
Without a value, company and fax are returned as an empty string in responses.
Create a contact
POST /handles creates a contact and sends it to the registry:
curl -X POST "$XCLOUD_DNS_API/handles" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"handle": {
"firstname": "Max",
"lastname": "Muster",
"company": "Example GmbH",
"street": "Musterstr. 1",
"pcode": "28217",
"city": "Bremen",
"country": "DE",
"phone": "+49 421 000000",
"email": "domains@example.com"
}
}'
The response contains the created contact in data; you can assign it to a domain right
away. If the registry rejects the data, the request fails with 422.
Change a contact
PATCH /handles/{id} changes the given attributes; attributes you omit remain
unchanged. As long as a domain uses the contact (in_use: true), firstname,
lastname, and company cannot be changed; the request then fails with 422. To
change the owner of a domain, create a new contact and assign it to the domain (see
Change a domain).
Delete a contact
DELETE /handles/{id} removes the contact from your project. A contact that a domain
still uses cannot be deleted (422).
Domains
| Method | Path | Description |
|---|---|---|
GET |
/domains |
List all domains |
GET |
/domains/{id} |
Show a domain |
POST |
/domains/check |
Check whether a domain is available |
POST |
/domains |
Register a domain |
POST |
/domains/transfer |
Transfer domains to xCloud |
PATCH |
/domains/{id} |
Change contacts or nameservers |
DELETE |
/domains/{id} |
Cancel a domain |
POST |
/domains/{id}/auto_renew |
Switch auto-renew on (undo a cancellation) |
DELETE |
/domains/{id}/auto_renew |
Switch auto-renew off (cancel) |
POST |
/domains/bulk_auto_renew |
Switch auto-renew on for several domains |
DELETE |
/domains/bulk_auto_renew |
Switch auto-renew off for several domains |
POST |
/domains/bulk_destroy |
Cancel several domains |
POST |
/domains/{id}/transfer_lock |
Set the transfer lock |
DELETE |
/domains/{id}/transfer_lock |
Remove the transfer lock |
POST |
/domains/{id}/auth_code |
Retrieve the auth code for a transfer away |
Domain attributes
| Attribute | Description |
|---|---|
id |
ID of the domain |
name |
Domain name, e.g. example.com |
state |
State of the domain, see below |
state_detail |
Additional information on the state, e.g. an error, or null |
order_number |
Order number at the registry, or null |
nameserver_mode |
zone or custom, see below |
nameservers |
The domain's own nameservers in mode custom |
zone_id |
ID of the linked xCloud zone, or null |
owner_handle |
Owner: object with id, registrar_handle, and label |
admin_handle |
Admin contact, same structure |
tech_handle |
Technical contact, same structure |
zone_handle |
Zone manager, same structure |
registered_at |
Time of registration, or null |
paid_until |
End of the paid term, or null |
auto_renew |
true as long as no cancellation is scheduled |
to_be_deleted_date |
Date on which the cancelled domain is released, or null |
transfer_lock |
Whether the transfer lock is set; null until it is known |
lock_synced_at |
Time the transfer lock was last synchronized, or null |
created_at |
Creation time |
Fetch a domain (GET /domains returns all domains in the same form):
curl "$XCLOUD_DNS_API/domains/$DOMAIN_ID" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN"
{
"data": {
"id": "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f51",
"name": "example.com",
"state": "registered",
"state_detail": null,
"order_number": "4711",
"nameserver_mode": "zone",
"nameservers": [],
"zone_id": "0b7c4f0e-2a51-4c4e-9a7f-5d3c1e8b9f10",
"owner_handle": {
"id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"registrar_handle": "MM12345-XCLOUD",
"label": "Example GmbH"
},
"admin_handle": {
"id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"registrar_handle": "MM12345-XCLOUD",
"label": "Example GmbH"
},
"tech_handle": {
"id": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e02",
"registrar_handle": "EM67890-XCLOUD",
"label": "Erika Mustermann"
},
"zone_handle": {
"id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"registrar_handle": "MM12345-XCLOUD",
"label": "Example GmbH"
},
"registered_at": "2026-09-14T10:31:22+02:00",
"paid_until": "2027-09-14",
"auto_renew": true,
"to_be_deleted_date": null,
"transfer_lock": true,
"lock_synced_at": "2026-10-20T03:30:41+02:00",
"created_at": "2026-09-14T10:29:05+02:00"
}
}
label is the name of the organisation or, without an organisation, the first and last
name.
The state attribute shows the progress of orders and changes:
| State | Meaning |
|---|---|
ordering |
The registration has been ordered and is in progress |
transfer_pending |
The transfer to xCloud is in progress |
registered |
The domain is active |
update_pending |
A change of contacts or nameservers is in progress |
deleting |
The domain is being deleted |
deleted |
The domain has been released |
failed |
The registry rejected the order or change; see state_detail |
Nameserver mode
When registering, transferring, or changing a domain, nameserver_mode defines which
nameservers the registry stores for the domain:
zone: the domain uses the nameservers of its xCloud zone. When registering or transferring, also send"create_zone": true: xCloud then creates a zone with the domain name or links an existing zone of the same name in your project.custom: the domain uses your own nameservers. Send at least two hostnames innameservers.
Check availability
POST /domains/check checks whether a domain can be registered. The name is sent
without an outer object:
curl -X POST "$XCLOUD_DNS_API/domains/check" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "example.com"}'
The response contains name, available (true or false), and the status
reported by the registry. Results are cached briefly.
Register a domain
POST /domains orders a domain at the registry.
| Attribute | Required | Description |
|---|---|---|
name |
yes | Domain name |
owner_handle_id |
yes | ID of the owner contact |
admin_handle_id |
no | ID of the admin contact; default: the owner |
tech_handle_id |
no | ID of the technical contact; default: the owner |
zone_handle_id |
no | ID of the zone manager; default: the owner |
nameserver_mode |
yes | zone or custom, see Nameserver mode |
nameservers |
for custom |
At least two nameserver hostnames |
create_zone |
no | true creates or links the xCloud zone of the domain |
Register a domain with its own xCloud zone and a separate technical contact:
curl -X POST "$XCLOUD_DNS_API/domains" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": {
"name": "example.com",
"owner_handle_id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"tech_handle_id": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e02",
"nameserver_mode": "zone",
"create_zone": true
}
}'
The order is processed in the background: the response shows the domain in state
ordering. As soon as the registry has confirmed the registration, the state changes to
registered; if the registry rejects it, to failed. With create_zone, zone_id
already contains the ID of the new or linked zone, in which you can create records right
away.
Transfer domains
POST /domains/transfer transfers one or more domains from another provider to xCloud.
For each domain, you need the auth code from the previous provider; the transfer lock
must be removed there. The attributes are the same as for a registration, but instead of
name, you send a list transfers with name and auth_code per domain, at most 40
domains per request. Contacts and nameserver settings apply to all domains of the
request.
Transfer two domains that keep using their own nameservers:
curl -X POST "$XCLOUD_DNS_API/domains/transfer" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": {
"transfers": [
{ "name": "example.com", "auth_code": "Xy7#kL9!pQ2r" },
{ "name": "example.net", "auth_code": "Ab3$dE6&fG8h" }
],
"owner_handle_id": "3f1e2d4c-5b6a-4789-8a0b-1c2d3e4f5a61",
"nameserver_mode": "custom",
"nameservers": ["ns1.example.org", "ns2.example.org"]
}
}'
A transfer request is all-or-nothing: if a single domain is invalid, no transfer is
started and the request fails with 422. The error messages name the affected domain,
so you can correct the request and send it again as a whole. On success, the response
lists all domains in state transfer_pending.
Change a domain
PATCH /domains/{id} changes the contacts or nameservers of a registered domain. You
can send the attributes owner_handle_id, admin_handle_id, tech_handle_id,
zone_handle_id, nameserver_mode, and nameservers; attributes you omit remain
unchanged. A role cannot be reset to "same as owner"; assign the owner's contact ID
explicitly instead.
Switch a domain to your own nameservers:
curl -X PATCH "$XCLOUD_DNS_API/domains/$DOMAIN_ID" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": {
"nameserver_mode": "custom",
"nameservers": ["ns1.example.org", "ns2.example.org"]
}
}'
The change is processed in the background (state update_pending). Changes are only
possible in state registered; otherwise the request fails with 422. Depending on the
TLD, changing the owner triggers an owner change that has to be confirmed again.
Cancel a domain and auto-renew
Domains renew automatically as long as auto_renew is true. DELETE /domains/{id}
cancels a domain at the end of its paid term, exactly like DELETE
/domains/{id}/auto_renew. Both return the changed domain in data:
- The domain remains
registeredand usable untilpaid_until.to_be_deleted_dateshows the date on which it is released, andauto_renewbecomesfalse. POST /domains/{id}/auto_renewundoes the cancellation. This is no longer possible within five days beforeto_be_deleted_date(422).- A domain in state
failed(a rejected order) is deleted immediately byDELETE /domains/{id}.
If the end of the term is not yet known (paid_until is null), the cancellation is
rejected with 422; try again later.
Bulk actions
Three endpoints act on several domains at once, at most 40 per request:
POST /domains/bulk_destroycancels the domains (likeDELETE /domains/{id})DELETE /domains/bulk_auto_renewswitches auto-renew offPOST /domains/bulk_auto_renewswitches auto-renew back on
All three take the same body:
curl -X POST "$XCLOUD_DNS_API/domains/bulk_destroy" \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": {
"ids": [
"1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f51",
"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c92"
]
}
}'
First, xCloud checks whether the action is possible for every domain. If it is not
possible for even one domain, nothing is executed and the request fails with 422,
listing the affected domains. Otherwise, the action is executed for each domain
individually. The response contains the domains in data and, in errors, the domains
for which the registry rejected the action, with name and message:
{
"data": [ { "id": "1d2e3f4a-...", "name": "example.com", "...": "..." } ],
"errors": [ { "name": "example.net", "message": "..." } ]
}
Transfer lock
POST /domains/{id}/transfer_lock sets the transfer lock at the registry, DELETE
/domains/{id}/transfer_lock removes it. While the lock is set, the domain cannot be
transferred to another provider. Both requests take effect immediately and are only
possible for domains in state registered.
Auth code
POST /domains/{id}/auth_code returns the auth code that you need to transfer the
domain to another provider:
{ "data": { "auth_code": "..." } }
For .de domains, every request generates a new auth code. The request is only possible
for domains in state registered. Only pass the auth code on to your new provider, and
remove the transfer lock before the transfer.