Skip to content

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 in nameservers.

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 registered and usable until paid_until. to_be_deleted_date shows the date on which it is released, and auto_renew becomes false.
  • POST /domains/{id}/auto_renew undoes the cancellation. This is no longer possible within five days before to_be_deleted_date (422).
  • A domain in state failed (a rejected order) is deleted immediately by DELETE /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_destroy cancels the domains (like DELETE /domains/{id})
  • DELETE /domains/bulk_auto_renew switches auto-renew off
  • POST /domains/bulk_auto_renew switches 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.