xCloud DNS API
Overview
With the xCloud DNS REST API, you manage zones, records, domains, and contacts from scripts, CI pipelines, or your own tools, just as in the DNS section of the web app. This documentation covers the following areas:
- Zones and records: create zones, manage records of all types, DynDNS, DNSSEC, delegated subdomains, zone file import and export
- Domains and contacts: check availability, register and transfer domains, change nameservers and contacts, auto-renew, transfer lock, auth code, and manage contacts (handles)
- Webhooks: events that xCloud sends to your own URL
- Prices: the public price list for domains
The API uses JSON for requests and responses and standard HTTP methods: GET reads,
POST creates or triggers actions, PATCH changes, and DELETE deletes.
Base URL
All paths in this documentation are relative to the following base URL:
https://app.xcloud.eu/api/dns/v1
For example, the zone list is available at https://app.xcloud.eu/api/dns/v1/zones.
Authentication
The API authenticates requests with API tokens. Create a token in the web app on
the API tokens tab of the DNS section (see API tokens). Tokens start
with xcdns_ and are only shown once when they are created. Store them securely, e.g.
in a password manager or your CI system's secret store.
Each token belongs to the project in which you created it. It only acts on the zones, records, domains, and contacts of that project; the paths therefore don't contain the project.
Send the token in the Authorization header of every request:
export XCLOUD_DNS_TOKEN='xcdns_...'
curl https://app.xcloud.eu/api/dns/v1/zones \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN"
API tokens and notifications are managed in the web app only. An
API token has no access to them; such requests are rejected with 403.
Permissions
Each token has an access level for three areas: No access, Read, or Write.
| Area | Covers |
|---|---|
| Records | Zones, records, and delegated subdomains (/zones, /records) |
| Domains | Domains (/domains) |
| Contacts | Contacts (/handles) |
Write includes Read. With Read, a token may use GET requests as well as
the domain availability check (POST /domains/check); all other requests require
Write.
Additional restrictions you can set per token:
- Zones: a token restricted to selected zones only sees those zones and their
records. Lists contain only the permitted zones; requests for other zones are
rejected with
403. Without a restriction, the token applies to all zones, including future ones. - IP allowlist: a token with an IP allowlist is only accepted from the listed IP
addresses or CIDR ranges; requests from other addresses are rejected with
401. - Validity: an expired or revoked token is rejected with
401, just like a missing or unknown token.
Requests
- Send request bodies as JSON with the header
Content-Type: application/json. - Most request bodies wrap their attributes in an object named after the resource, e.g.
{"zone": {...}},{"record": {...}},{"domain": {...}}, or{"handle": {...}}. - Resources are identified by their
id(a UUID string), not by their name. How to find the ID for a name is described under Lists.
Responses
Successful responses carry status 200 and wrap their content in data:
{
"data": {
"id": "0b7c4f0e-2a51-4c4e-9a7f-5d3c1e8b9f10",
"name": "example.com",
"...": "..."
}
}
Timestamps use ISO 8601 format. You'll find complete example responses with the individual resources.
DELETE requests that delete something return 200 without content. This applies to
zones, records, subdomains, and contacts, and also to
POST /zones/{zone_id}/records/bulk_destroy. DELETE requests that switch a feature off
or cancel a domain, on the other hand, return the changed resource in data:
DELETE /domains/{id}andDELETE /domains/{id}/auto_renew(cancel a domain)DELETE /domains/{id}/transfer_lock(remove the transfer lock)DELETE /zones/{id}/dnssec(switch DNSSEC off)DELETE /records/{id}/dyndns(switch DynDNS off)
Like all bulk actions for domains, DELETE /domains/bulk_auto_renew returns the domains
in data and rejected domains in errors (see Bulk actions).
Lists
Lists return an array in data and are always complete: there is no pagination and,
apart from the zone_id parameter of GET /records, no filtering. To find the ID of a
zone or domain by its name, fetch the list and look up the name on your side, e.g. with
jq:
curl -s https://app.xcloud.eu/api/dns/v1/zones \
-H "Authorization: Bearer $XCLOUD_DNS_TOKEN" |
jq -r '.data[] | select(.name == "example.com") | .id'
Asynchronous operations
Some operations take effect in the background. The response then describes the state directly after the request; query the resource again later to see the result:
- Zones start in state
pendingand becomeactiveonce they are set up on the nameservers. - Domain registrations, transfers, and changes are processed by the registry. The
domain's
stateshows the progress (ordering,transfer_pending,update_pending, thenregisteredorfailed). - DNSSEC signing, the delegation check, and the zone transfer of secondary zones are queued.
Record changes are active on all nameservers after 60 seconds at the latest.
Errors
Errors are returned with an HTTP status code and an error object:
| Status | Meaning |
|---|---|
400 |
A required parameter is missing, or a list (e.g. of IDs) is empty or too long |
401 |
No valid token, or the sender's address is not in the token's IP allowlist |
402 |
A validated payment method is required |
403 |
The token lacks the required permission or zone, or the action is not allowed |
404 |
The resource does not exist or does not belong to the token's project |
422 |
The request is invalid, e.g. a validation error or a disallowed state |
If DNS is locked in the token's project, all requests are answered with 404.
Validation errors (422) list each problem with the affected field in name:
{
"error": {
"status": "422",
"errors": [
{ "name": "content", "message": "is not a valid IPv4 address" }
]
}
}
General errors carry a message instead:
{
"error": {
"status": "403",
"message": "Forbidden"
}
}
Webhooks
xCloud can call your own URL when something happens with your domains and zones. You
set the webhook URL and the events for which xCloud calls it in the web app on the
Notifications tab (see Notifications). The URL must start with
https://.
For each event, xCloud sends a POST request with the header
Content-Type: application/json and the following body:
{
"event": "transfer_update",
"occurred_at": "2026-10-20T09:14:02+02:00",
"data": {
"domain": "example.com",
"status": "completed",
"detail": ""
}
}
event: name of the event, see belowoccurred_at: time of the eventdata: details of the event; which fields it contains depends on the event
Events
The name of the event in the web app is given in parentheses.
expiry(Domain expiring): a domain with auto-renew switched off will be released within the configured expiry lead time. The event is sent once per term. Fields:domain,expires_on(release date).operation_failed(Renewal failed): ordering a domain has failed. Fields:domain,operation(registration),detail(error message).delegation_broken(Delegation broken): the delegation check of a zone was fine so far and now finds different nameservers. Fields:zone,observed(the nameservers found at the registry, comma-separated).transfer_update(Transfer status): a transfer to xCloud has completed (status:completed) or could not be started (status:failed, reason indetail). Fields:domain,status,detail.zone_changed(Zone changed): records of a zone were created, changed, deleted, or imported, in the web app or through the API. Fields:zone.dnssec_update(DNSSEC key expiring): xCloud has published the DS records of a domain registered through xCloud at the registry. Fields:zone,status(ds_published).
Signature
Webhooks are not signed: the request contains neither a signature nor credentials. Use a URL that is hard to guess, and treat the content as a hint. If needed, query the authoritative state through the API.
Prices
GET /prices returns the prices for domains per extension (TLD). The endpoint is public
and does not require a token:
curl https://app.xcloud.eu/api/dns/v1/prices
The response contains one entry per extension in data:
| Attribute | Description |
|---|---|
tld |
Extension without the dot, e.g. de |
registration_fee |
Price for the registration |
transfer_fee |
Price for the transfer to xCloud |
annual_fee |
Annual price |
restore_fee |
Price for restoring a deleted domain |
Prices are returned as strings with six decimal places, or as null if no price is set
for the extension.