DNS Management

7 views Markdown

The eleven endpoints behind a domain's DNS records, child nameservers and DNSSEC signatures.

Overview

These eleven endpoints run a domain's name resolution: which name points where, the domain's own nameservers, and signature validation.

Three separate jobs sit together. Child nameservers define the domain's own servers. DNS records say which name goes where. DNSSEC makes those answers signed.

None of it keeps a local copy: every read goes to the registry and every write is live at once.

Reference

Listing the Child Nameservers

get/api/v1/admin/services/{id}/domain/child-nameservers
Services/GetDomainChildNameservers admin

Returns the nameservers defined under the domain itself.

Response fields data[] — 2
nsstringThe child nameserver's name.
ipstringThe address it resolves to.
Errors 5
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
module_failed500The registry could not complete the operation.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// When the module cannot list them a LOCAL copy is returned, which can drift from the registry.
$response = Api::Services()->GetDomainChildNameservers(['id' => 520]);

Adding a Child Nameserver

post/api/v1/admin/services/{id}/domain/child-nameservers
Services/AddDomainChildNameserver admin 201

Defines a new nameserver under the domain.

Body 2
nsstringrequiredThe nameserver's name.
ipstringrequiredThe address it resolves to. Validated as IPv4 or IPv6.
Response fields data — 2
nsstringThe added child nameserver's name. Returned with 201.
ipstringThe address it resolves to.
Errors 7
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
cns_fields_required422The name or the address was empty.
invalid_ip422The address is not a valid IP.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ns":"ns1.example.com","ip":"203.0.113.10"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ns: 'ns1.example.com', ip: '203.0.113.10' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ns' => 'ns1.example.com',
        'ip' => '203.0.113.10',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Defining a child nameserver does not START USING it; point the domain at it separately.
Api::Services()->AddDomainChildNameserver([
    'id' => 520,
    'ns' => 'ns1.example.com',
    'ip' => '203.0.113.10',
]);

Api::Services()->SetDomainNameservers([
    'id'  => 520,
    'ns1' => 'ns1.example.com',
    'ns2' => 'ns2.example.com',
]);

Updating a Child Nameserver

put/api/v1/admin/services/{id}/domain/child-nameservers
Services/UpdateDomainChildNameserver admin found by its old value

Finds an existing child nameserver and replaces it with new values.

Body 4
old_nsstringrequiredThe current name of the record to change.
old_ipstringThe current address. It tells apart several records sharing a name.
new_nsstringrequiredThe new name.
new_ipstringrequiredThe new address.
Response fields data — 2
nsstringThe child nameserver's name after the change.
ipstringThe address it now resolves to.
Errors 7
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
cns_fields_required422One of the required fields was empty.
invalid_ip422The new address is not a valid IP.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"old_ns":"ns1.example.com","old_ip":"203.0.113.10","new_ns":"ns1.example.com","new_ip":"203.0.113.20"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    old_ns: 'ns1.example.com',
    old_ip: '203.0.113.10',
    new_ns: 'ns1.example.com',
    new_ip: '203.0.113.20',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'old_ns' => 'ns1.example.com',
        'old_ip' => '203.0.113.10',
        'new_ns' => 'ns1.example.com',
        'new_ip' => '203.0.113.20',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The record is found by its old value, not an id: get the old name wrong and nothing matches.
Api::Services()->UpdateDomainChildNameserver([
    'id'     => 520,
    'old_ns' => 'ns1.example.com',
    'old_ip' => '203.0.113.10',
    'new_ns' => 'ns1.example.com',
    'new_ip' => '203.0.113.20',
]);

Deleting a Child Nameserver

delete/api/v1/admin/services/{id}/domain/child-nameservers
Services/DeleteDomainChildNameserver admin

Removes a child nameserver definition.

Body 2
nsstringrequiredName of the record to delete.
ipstringThe address. Some modules need it to match.
Response fields data — 2
deletedboolWhether the delete succeeded.
nsstringName of the deleted record.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
module_failed500The registry could not complete the operation.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ns":"ns1.example.com"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ns: 'ns1.example.com' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/child-nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['ns' => 'ns1.example.com']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Deleting a child nameserver that is in use can leave the domain unreachable.
Api::Services()->DeleteDomainChildNameserver(['id' => 520, 'ns' => 'ns1.example.com']);

Listing the DNS Records

get/api/v1/admin/services/{id}/domain/dns-records
Services/GetDomainDnsRecords admin straight from the registry

Returns the domain's DNS records. No local copy is kept.

Response fields data[] — 6
identitystringThe id the module gives the record. Its shape differs between modules.
typestringThe record type.
namestringThe record name or subdomain.
valuestringThe record value.
ttlintTime to live, in seconds.
priorityintPriority. Meaningful on mail records.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
module_failed500The registry could not complete the operation.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/services/520/domain/dns-records' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dns-records', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The 'identity' needed to update or delete comes from HERE; it cannot be produced elsewhere.
$records = Api::Services()->GetDomainDnsRecords(['id' => 520])['data'];

$www = current(array_filter($records, fn (array $r): bool => $r['name'] === 'www'));
Response
{
  "data": [
    {
      "identity": "1001",
      "type": "A",
      "name": "@",
      "value": "203.0.113.10",
      "ttl": 3600,
      "priority": 0
    }
  ]
}

Adding a DNS Record

post/api/v1/admin/services/{id}/domain/dns-records
Services/AddDomainDnsRecord admin 201

Adds a new DNS record to the domain.

Body 5
typestringrequiredThe record type.
namestringrequiredThe record name or subdomain.
valuestringrequiredThe record value.
ttlintTime to live, in seconds.
priorityintPriority.
Response fields data — 2
addedboolThe record was added. Returned with 201.
A module that returns the added record sends that record instead. Read the shape you get; do not count on added.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
dns_fields_required422One of the required fields was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/services/520/domain/dns-records' \
  -H "Authorization: Bearer $API_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/admin/services/520/domain/dns-records', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'A',
    name: 'www',
    value: '203.0.113.10',
    ttl: 3600,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'  => 'A',
        'name'  => 'www',
        'value' => '203.0.113.10',
        'ttl'   => 3600,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The response may NOT carry the record it added; read the list again to learn its identity.
Api::Services()->AddDomainDnsRecord([
    'id'    => 520,
    'type'  => 'A',
    'name'  => 'www',
    'value' => '203.0.113.10',
]);

$records = Api::Services()->GetDomainDnsRecords(['id' => 520])['data'];

Updating a DNS Record

put/api/v1/admin/services/{id}/domain/dns-records
Services/UpdateDomainDnsRecord admin not on every module

Changes an existing DNS record. It does not run if the module offers no update.

Body 6
typestringrequiredThe record type.
namestringrequiredThe record name.
valuestringrequiredThe new value.
identitystringThe record id from the list. This is the safest way to hit the right record.
ttlintTime to live.
priorityintPriority.
Response fields data — 2
updatedboolThe record was updated.
A module that returns the updated record sends that record instead. Read the shape you get; do not count on updated.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
dns_fields_required422One of the required fields was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/dns-records' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"A","name":"www","value":"203.0.113.20","identity":"1001"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dns-records', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'A',
    name: 'www',
    value: '203.0.113.20',
    identity: '1001',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'     => 'A',
        'name'     => 'www',
        'value'    => '203.0.113.20',
        'identity' => '1001',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A module with no update answers 'not_supported'; fall back to delete-then-add.
$response = Api::Services()->UpdateDomainDnsRecord([
    'id'       => 520,
    'type'     => 'A',
    'name'     => 'www',
    'value'    => '203.0.113.20',
    'identity' => '1001',
]);

Deleting a DNS Record

delete/api/v1/admin/services/{id}/domain/dns-records
Services/DeleteDomainDnsRecord admin live immediately

Removes a DNS record.

Body 4
typestringrequiredThe record type.
namestringThe record name. It narrows the match.
valuestringThe record value. It narrows the match.
identitystringThe record id from the list.
Response fields data
deletedboolThe record was deleted.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
dns_fields_required422The record type was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/services/520/domain/dns-records' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"A","name":"www","identity":"1001"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dns-records', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ type: 'A', name: 'www', identity: '1001' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dns-records');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'     => 'A',
        'name'     => 'www',
        'identity' => '1001',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Sending only the type can sweep up other records of the SAME TYPE; give the identity too.
Api::Services()->DeleteDomainDnsRecord([
    'id'       => 520,
    'type'     => 'A',
    'name'     => 'www',
    'identity' => '1001',
]);

Listing the DNSSEC Records

get/api/v1/admin/services/{id}/domain/dnssec
Services/GetDomainDnssecRecords admin

Returns the domain's DNSSEC signing records.

Response fields data[] — 5
identitystringThe id the module gives the record.
digeststringThe digest value.
key_tagintThe key tag.
digest_typeintThe digest type. The allowed values come from the module settings.
algorithmintThe algorithm. The allowed values come from the module settings.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
module_failed500The registry could not complete the operation.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/services/520/domain/dnssec' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dnssec', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Services()->GetDomainDnssecRecords(['id' => 520]);

Adding a DNSSEC Record

post/api/v1/admin/services/{id}/domain/dnssec
Services/AddDomainDnssecRecord admin 201

Adds a DNSSEC signing record to the domain.

Body 4
digeststringrequiredThe digest value.
key_tagintrequiredThe key tag. It has to be above zero.
digest_typeintrequiredThe digest type. It has to be on the module's allowed list.
algorithmintrequiredThe algorithm. It has to be on the module's allowed list.
Response fields data — 2
addedboolThe record was added. Returned with 201.
A module that returns the added record sends that record instead. Read the shape you get; do not count on added.
Errors 8
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
dnssec_fields_required422One of the required fields was empty.
invalid_digest_type422The digest type is not defined in the module settings.
invalid_algorithm422The algorithm is not defined in the module settings.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/services/520/domain/dnssec' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"digest":"49FD46E6C4B45C55D4AC","key_tag":12345,"digest_type":2,"algorithm":13}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dnssec', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    digest: '49FD46E6C4B45C55D4AC',
    key_tag: 12345,
    digest_type: 2,
    algorithm: 13,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'digest'      => '49FD46E6C4B45C55D4AC',
        'key_tag'     => 12345,
        'digest_type' => 2,
        'algorithm'   => 13,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The allowed digest type and algorithm come from the MODULE SETTINGS, not every value in the standard.
Api::Services()->AddDomainDnssecRecord([
    'id'          => 520,
    'digest'      => $digest,
    'key_tag'     => 12345,
    'digest_type' => 2,
    'algorithm'   => 13,
]);

Deleting a DNSSEC Record

delete/api/v1/admin/services/{id}/domain/dnssec
Services/DeleteDomainDnssecRecord admin validation can break

Removes a DNSSEC signing record.

Body 5
digeststringrequiredThe digest value.
key_tagintrequiredThe key tag.
digest_typeintThe digest type. It narrows the match.
algorithmintThe algorithm. It narrows the match.
identitystringThe record id from the list.
Response fields data
deletedboolThe record was deleted.
Errors 6
not_found404No such service.
not_domain422The service is not a domain.
no_module422No registrar module is attached.
not_supported422The module does not support this operation.
dnssec_fields_required422The digest or the key tag was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/services/520/domain/dnssec' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"digest":"49FD46E6C4B45C55D4AC","key_tag":12345}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/dnssec', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ digest, key_tag: 12345 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/dnssec');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'digest'  => $digest,
        'key_tag' => 12345,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Deleting the last DS record turns off signature validation; add the new one first.
Api::Services()->AddDomainDnssecRecord(['id' => 520] + $newRecord);
Api::Services()->DeleteDomainDnssecRecord(['id' => 520] + $oldRecord);

Pitfalls

The record id only comes from the list

The identity on the update and delete endpoints is what the list returned. You cannot make one up, and its shape differs between modules. The add response may not carry the record it created, so read the list again to learn the id.

A delete without an id can sweep up more

Only the record type is required on the delete. Sent without a name, a value or an id, it can sweep up other records of the same type, because the module does the matching. Pin down which record you mean with an id before deleting.

Not every module can update a DNS record

The update only runs when the module offers it; otherwise it answers not_supported. On such a module the only route is delete then add, which means a brief gap between the two.

The allowed DNSSEC values come from the module

The digest type and the algorithm do not accept every value in the standard. The allowed set is defined in the module's own settings, and a value absent from that list answers invalid_algorithm.

Writes go live at once

There is no draft or approval step here; a change you make reaches the registry and starts propagating. Deleting a child nameserver that is in use can leave the domain unreachable. Deleting the last DNSSEC record turns off signature validation. Undoing a change that went wrong takes as long as the caches hold it.

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.