Name Servers and Records

1 views Markdown

The fourteen endpoints deciding where a domain points.

Overview

Where a domain points is decided in two layers. The name servers say which server answers the questions, and the records on that server say what the answer is.

That split reaches the endpoints: changing name servers is open on every domain, while managing the records here wants the name management add-on.

Three more endpoints go further. Running your own name servers means defining them under the domain, and the signature records prove the answers were not changed on the way.

Reference

Reading the Name Servers

get/api/v1/client/domains/{domain}/nameservers
Domains/GetNameservers the key's owner

Returns a domain's name servers and the account's default set.

Response fields data — 3
nameserversstring[]The domain's current set. Four entries at most, and empty slots drop out.
defaultstring[]The account's default set.
is_customboolWhether the set differs from the default. The comparison ignores case.
Errors 2
not_found404No such domain, it is not yours, or access to it is restricted.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/domains/example.com/nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/nameservers`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
if (data.is_custom) showResetToDefault(data.default);
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This endpoint wants NO ADD-ON: changing name servers is open on every domain, unlike the records.
$n = Kernel::internal('client:Domains/GetNameservers',
    ['owner_id' => $uid, 'domain' => $domain])['data'];
$set = $n['nameservers'];

Changing the Name Servers

put/api/v1/client/domains/{domain}/nameservers
Domains/UpdateNameservers it moves the site

Changes a domain's name servers at the provider.

Body 1
nameserversstring[]reqThe new set. Two to four valid host names, and empty entries drop before the count.
Response fields data — 1
nameserversstring[]The set the provider accepted. It comes back in lower case.
Errors 6
not_found404No such domain, it is not yours, or access to it is restricted.
not_actionable422The domain is not live.
nameservers_invalid422Fewer than two or more than four entries, or one is not a valid host name.
nameservers_not_supported422The provider module cannot write them.
nameservers_rejected422A hook refused the change.
nameservers_failed422The provider refused the change.
Request
curl -X PUT 'https://panel.example.com/api/v1/client/domains/example.com/nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"nameservers":["ns1.example.net","ns2.example.net"]}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/nameservers`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ nameservers: list }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['nameservers' => $list]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Changing the name servers MOVES THE SITE: the records on the old ones stop counting.
// Confirm the records are ready on the new servers first.
Kernel::internal('client:Domains/UpdateNameservers',
    ['owner_id' => $uid, 'domain' => $domain, 'nameservers' => $list]);

Reading the Default Name Servers

get/api/v1/client/domains/default-nameservers
Domains/GetDefaultNameservers account-wide

Returns the default set the account applies to new domains.

Response fields data — 1
nameserversstring[]The default set saved. An empty list comes where none was saved.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/domains/default-nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/domains/default-nameservers', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
if (! data.nameservers.length) promptToSetDefaults();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/default-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The default set applies to NEW registrations and never reaches back to the domains you hold.
$d = Kernel::internal('client:Domains/GetDefaultNameservers', ['owner_id' => $uid])['data'];

Saving the Default Name Servers

put/api/v1/client/domains/default-nameservers
Domains/UpdateDefaultNameservers account-wide

Saves the default set for new domains.

Body 1
nameserversstring[]reqThe default set. Two to four valid host names, trimmed and lowered to lower case.
Response fields data — 1
nameserversstring[]The set saved.
Errors 2
nameservers_invalid422The entry count or form is invalid.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/client/domains/default-nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"nameservers":["ns1.example.net","ns2.example.net"]}'
const res = await fetch('https://panel.example.com/api/v1/client/domains/default-nameservers', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ nameservers: list }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/default-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['nameservers' => $list]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This setting touches NO domain: it is the starting value for the registrations that follow.
Kernel::internal('client:Domains/UpdateDefaultNameservers',
    ['owner_id' => $uid, 'nameservers' => $list]);

Listing Your Own Name Servers

get/api/v1/client/domains/{domain}/child-nameservers
Domains/GetChildNameservers it reads from the provider

Returns the name servers defined under the domain.

Response fields data[] — 2
nsstringThe server's full name. It usually sits under the domain itself.
ipstringThe address on record. It comes empty where the provider reports none.
Errors 4
not_found404No such domain, it is not yours, or access to it is restricted.
child_ns_not_supported422The provider module lacks the support.
child_ns_failed500The live read failed with no record to fall back to.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/domains/example.com/child-nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/child-nameservers`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// These records are for running YOUR OWN name servers; an ordinary domain wants none.
$g = Kernel::internal('client:Domains/GetChildNameservers',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

Defining Your Own Name Server

post/api/v1/client/domains/{domain}/child-nameservers
Domains/CreateChildNameserver it writes to the registry

Defines a name server under the domain.

Body 2
hoststringreqThe server's full name. It is lowered to lower case.
ipstringreqThe server's address. Its form is checked.
Response fields data[] — 2
dataarrayThe list read afresh. Same shape as the listing endpoint.
Errors 8
not_found404No such domain, it is not yours, or access to it is restricted.
not_actionable422The domain is not live.
child_ns_fields422The name or the address was not sent.
child_ns_host_invalid422The name is not a valid host name.
child_ns_ip_invalid422The address is not valid.
child_ns_not_supported422The provider module lacks the support.
child_ns_rejected422A hook refused the change.
child_ns_failed422The provider refused the record.
Request
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/child-nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"host":"ns1.example.com","ip":"203.0.113.10"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/child-nameservers`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ host, ip }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(compact('host', 'ip')),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Defining one is not enough: write the name server set too to POINT the domain at it.
Kernel::internal('client:Domains/CreateChildNameserver',
    ['owner_id' => $uid, 'domain' => $domain, 'host' => $host, 'ip' => $ip]);

Kernel::internal('client:Domains/UpdateNameservers',
    ['owner_id' => $uid, 'domain' => $domain, 'nameservers' => [$host, $host2]]);

Removing Your Own Name Server

delete/api/v1/client/domains/{domain}/child-nameservers
Domains/DeleteChildNameserver it writes to the registry

Removes a name server defined under the domain.

Body 2
hoststringreqThe name of the server to remove.
ipstringThe address. Send it where the provider keys on the name and the address together.
Response fields data[] — 2
dataarrayThe list read afresh.
Errors 6
not_found404No such domain, it is not yours, or access to it is restricted.
not_actionable422The domain is not live.
child_ns_fields422The server name was not sent.
child_ns_not_supported422The provider module lacks the support.
child_ns_rejected422A hook refused the removal.
child_ns_failed422The provider refused the removal.
Request
curl -X DELETE 'https://panel.example.com/api/v1/client/domains/example.com/child-nameservers' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"host":"ns1.example.com"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/child-nameservers`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ host }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['host' => $host]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Removing a server that is STILL IN USE can leave other domains unreachable.
Kernel::internal('client:Domains/DeleteChildNameserver',
    ['owner_id' => $uid, 'domain' => $domain, 'host' => $host]);

Listing the DNS Records

get/api/v1/client/domains/{domain}/dns-records
Domains/GetDnsRecords an add-on is needed

Reads a domain's DNS records from the provider.

Response fields data[] — 6
identitystringThe record id on the provider's side. Some providers know a record by its type, name and value, and it comes empty there.
typestringThe record type.
namestringThe label the record answers for.
valuestringWhat the record holds.
ttlintThe lifetime in seconds. It comes zero where the provider reports none.
prioritystringThe priority on mail and service records. It comes empty on other types.
Errors 6
not_found404No such domain, it is not yours, or access to it is restricted.
addon_required422The name management add-on was not bought. An unpaid invoice or an extension that does not offer it fall at the same gate.
addon_pending422The add-on invoice is unpaid. Its id comes in the answer's detail.
dns_not_supported422The provider module cannot read records.
dns_rejected422A hook refused the read.
dns_failed500The provider read failed.
Request
curl 'https://panel.example.com/api/v1/client/domains/example.com/dns-records' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dns-records`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

if (res.status === 422) return offerAddon(await res.json());

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The record id can come EMPTY: that provider knows a record by its type, name and value.
$rows = Kernel::internal('client:Domains/GetDnsRecords',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

$keyed = (bool) ($rows[0]['identity'] ?? '');

Adding a DNS Record

post/api/v1/client/domains/{domain}/dns-records
Domains/CreateDnsRecord an add-on is needed

Adds a new DNS record to the domain.

Body 5
typestringreqThe record type.
namestringreqThe label it answers for. The form accepted follows the provider.
valuestringreqWhat the record holds.
ttlintThe lifetime. The provider's default stands in when left out.
priorityintThe priority. It is ignored outside mail and service records.
Response fields data[] — 6
dataarrayThe list read afresh after the add. Same shape as the listing endpoint.
Errors 7
not_found404No such domain, it is not yours, or access to it is restricted.
addon_required422The name management add-on was not bought. An unpaid invoice or an extension that does not offer it fall at the same gate.
not_actionable422The domain is not live.
dns_fields422The type, the name or the value is missing.
dns_not_supported422The provider module cannot add records.
dns_rejected422A hook refused the add.
dns_failed422The provider refused the record.
Request
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/dns-records' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"A","name":"www","value":"203.0.113.10","ttl":3600}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dns-records`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ type: 'A', name: 'www', value: ip }),
});

const { data } = await res.json();
renderRecords(data);
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($record),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The answer returns THE FRESH LIST: refresh the screen from it rather than reading again.
$rows = Kernel::internal('client:Domains/CreateDnsRecord',
    ['owner_id' => $uid, 'domain' => $domain] + $record)['data'];

Editing a DNS Record

put/api/v1/client/domains/{domain}/dns-records
Domains/UpdateDnsRecord it keys on the id

Changes a DNS record that exists.

Body 6
identitystringThe record id from the listing. Always send it where the listing gave one.
typestringreqThe record type.
namestringreqThe record label.
valuestringreqThe new content.
ttlintThe lifetime.
priorityintThe priority.
Response fields data[] — 6
dataarrayThe list read afresh after the edit.
Errors 7
not_found404No such domain, it is not yours, or access to it is restricted.
addon_required422The name management add-on was not bought. An unpaid invoice or an extension that does not offer it fall at the same gate.
not_actionable422The domain is not live.
dns_fields422The type, the name or the value is missing.
dns_edit_not_supported422The provider module cannot edit. Remove and add again on such a provider.
dns_rejected422A hook refused the edit.
dns_failed422The provider refused the change.
Request
curl -X PUT 'https://panel.example.com/api/v1/client/domains/example.com/dns-records' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"identity":"42","type":"A","name":"www","value":"203.0.113.20"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dns-records`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ identity: rec.identity, ...next }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($record),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Editing is ABSENT on some providers: remove and add again where the capability list closes it.
$d = Kernel::internal('client:Domains/GetDomain',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

if (! $d['capabilities']['dns_edit']) { /* delete + create */ }

Removing a DNS Record

delete/api/v1/client/domains/{domain}/dns-records
Domains/DeleteDnsRecord the match narrows

Removes a DNS record.

Body 4
typestringreqThe type of the record to remove.
namestringThe record label. It narrows the match.
valuestringThe record content. It narrows the match.
identitystringThe record id from the listing. It is the surest selector.
Response fields data[] — 6
dataarrayThe list read afresh after the removal.
Errors 6
not_found404No such domain, it is not yours, or access to it is restricted.
addon_required422The name management add-on was not bought. An unpaid invoice or an extension that does not offer it fall at the same gate.
not_actionable422The domain is not live.
dns_fields422The record type was not sent.
dns_rejected422A hook refused the removal.
dns_failed422The provider refused the removal.
Request
curl -X DELETE 'https://panel.example.com/api/v1/client/domains/example.com/dns-records' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"identity":"42","type":"A","name":"www"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dns-records`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ identity: rec.identity, type: rec.type, name: rec.name }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($selector),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Sending the TYPE ALONE can remove MORE THAN ONE record of it: add the id, or the name and value.
Kernel::internal('client:Domains/DeleteDnsRecord', ['owner_id' => $uid, 'domain' => $domain,
    'identity' => $rec['identity'], 'type' => $rec['type'],
    'name' => $rec['name'], 'value' => $rec['value']]);

Listing the Signature Records

get/api/v1/client/domains/{domain}/dnssec
Domains/GetDnssecRecords it reads from the provider

Returns a domain's signature verification records.

Response fields data[] — 5
identitystringThe row id on the provider's side.
digeststringThe signature digest.
key_tagintThe tag of the key the digest points at.
digest_typeintThe digest algorithm number. The set allowed comes from the provider module.
algorithmintThe key algorithm number.
Errors 4
not_found404No such domain, it is not yours, or access to it is restricted.
dnssec_not_supported422The provider module lacks signature support.
dnssec_failed500The provider read failed.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/domains/example.com/dnssec' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dnssec`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
const signed = data.length > 0;
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// These want NO ADD-ON and do want provider support; confirm it from the capability list.
$ds = Kernel::internal('client:Domains/GetDnssecRecords',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

Adding a Signature Record

post/api/v1/client/domains/{domain}/dnssec
Domains/CreateDnssecRecord the algorithm is checked

Adds a signature verification record to the domain.

Body 4
key_tagintreqThe key tag.
algorithmintreqThe key algorithm number. It has to sit in the set the module reports.
digest_typeintreqThe digest algorithm number. It has to sit in the set the module reports.
digeststringreqThe signature digest.
Response fields data[] — 5
dataarrayThe set read afresh.
Errors 8
not_found404No such domain, it is not yours, or access to it is restricted.
not_actionable422The domain is not live.
dnssec_fields422One of the required fields is missing.
dnssec_not_supported422The provider module cannot add.
dnssec_digest_type_invalid422The digest algorithm is outside the set allowed. The numbers allowed come in the answer's detail.
dnssec_algorithm_invalid422The key algorithm is outside the set allowed.
dnssec_rejected422A hook refused the add.
dnssec_failed422The provider refused the record.
Request
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/dnssec' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"key_tag":12345,"algorithm":13,"digest_type":2,"digest":"A1B2C3"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dnssec`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(ds),
});

if (res.status === 422) showAllowed(await res.json());
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($ds),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The set of algorithms allowed follows THE PROVIDER: read the refusal and show the allowed list.
try     { Kernel::internal('client:Domains/CreateDnssecRecord',
              ['owner_id' => $uid, 'domain' => $domain] + $ds); }
catch   (\Throwable $e) { $allowed = $e->details['allowed'] ?? []; }

Removing a Signature Record

delete/api/v1/client/domains/{domain}/dnssec
Domains/DeleteDnssecRecord it can break the site

Removes a signature verification record.

Body 5
digeststringreqThe digest of the record to remove.
key_tagintreqThe record's key tag.
digest_typeintIt narrows the match.
algorithmintIt narrows the match.
identitystringThe row id from the listing. It is the surest selector.
Response fields data[] — 5
dataarrayThe set read afresh after the removal.
Errors 6
not_found404No such domain, it is not yours, or access to it is restricted.
not_actionable422The domain is not live.
dnssec_fields422The digest or the key tag is missing.
dnssec_not_supported422The provider module cannot remove.
dnssec_rejected422A hook refused the removal.
dnssec_failed422The provider refused the removal.
Request
curl -X DELETE 'https://panel.example.com/api/v1/client/domains/example.com/dnssec' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"key_tag":12345,"digest":"A1B2C3"}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/dnssec`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ key_tag: ds.key_tag, digest: ds.digest }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($selector),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Unsign the zone BEFORE removing the last record: the wrong order leaves the domain unresolvable.
$ds = Kernel::internal('client:Domains/GetDnssecRecords',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

$isLast = count($ds) === 1;

Pitfalls

Records want the add-on and name servers do not

The DNS record endpoints sit behind the name management add-on. Unbought, or with its invoice unpaid, they all refuse. The answer says which case it is. The name server endpoints pass no such gate and work on every domain.

Changing name servers leaves the records behind

Changing the name server set sends the domain's questions to a different server. The records you entered here stay on the old one and nobody asks them any more. Do not change the set before confirming the records are ready on the new servers.

The record id does not come from every provider

Some providers key a record by an id and others know it by its type, name and value. In the second case the id comes empty. Always send the id on an edit or a removal where one exists. Give the full triple where none does, or the wrong record can be picked.

Sending the type alone can remove several

Only the record type is required on a removal, while the name and the value narrow the match. Sending the type alone can take several records of that type at once. Add the name and the value even where you hold no id.

Removing a signature in the wrong order breaks the domain

While signature verification is on, the record at the registry and the signature in the zone have to agree. Removing the last record while the zone is still signed, or unsigning the zone while the record stands, leaves the domain unresolvable. Unsign the zone first and remove the record after.

Your own name server wants two steps

Defining a name server under the domain only brings it into being. The domain uses it once you write the name server set as well. Skipping that second step leaves the definition at the registry with nothing changed.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.