The Domains You Own

4 views Markdown

The five endpoints that read, set, renew and extend a domain you own.

Overview

A domain is a service too, and it carries endpoints of its own: the service endpoints answer not found for it. These five read a domain you own, set it, renew it and open its add-ons.

The address takes either the domain name itself or the service number, and both open the same record.

The capability list in the detail says which jobs can be done. It differs from provider to provider, so the interface should be built from that list.

Reference

Listing the Domains

get/api/v1/client/domains
Domains/GetDomains the key's owner

Returns the account's domains.

Query 5
pageintWhich page.
limitintRows per page. 100 at the most.
statusstringThe state filter. It takes badge groups.
tldstringThe domains on this extension alone.
searchstringSearches the domain name.
Response fields data[] — 11 + meta — 4
idintThe domain's service id.
namestringThe full domain name.
tldstringThe extension. The dots stay in a multi-part one.
statusstringThe raw service state.
auto_renewboolAutomatic renewal for this domain.
lockedboolThe last known transfer lock value. The live one is read from the lock endpoint.
whois_privacyboolThe last known registration privacy value.
priceobjectThe price per period.
yearsintThe term in years.
registered_atstringThe day it was registered.
due_datestringThe day it expires and renews.
totalintHow many domains there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/domains?tld=com' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/domains?status=active', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
const expiring = data.filter((d) => d.due_date && d.due_date < horizon);
$ch = curl_init('https://panel.example.com/api/v1/client/domains?status=active');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The lock and privacy values are a MIRROR: the last value saved rather than the current one.
$rows = Kernel::internal('client:Domains/GetDomains', ['owner_id' => $uid])['data'];
// for the live one: client:Domains/GetTransferLock

Reading a Domain

get/api/v1/client/domains/{domain}
Domains/GetDomain a capability probe

Returns a domain, what its provider can do and the renewal terms.

Response fields data — 17
idintThe domain's service id.
namestringThe full domain name.
tldstringThe extension. The dots stay in a multi-part one.
statusstringThe raw service state.
auto_renewboolAutomatic renewal for this domain.
lockedboolThe last known transfer lock value. The live one is read from the lock endpoint.
whois_privacyboolThe last known registration privacy value.
priceobjectThe price per period.
yearsintThe term in years.
registered_atstringThe day it was registered.
due_datestringThe day it expires and renews.
order_idintThe order that brought it into being.
auto_renew_lockedboolWhether account-wide automatic payment is on. The per-domain switch cannot move while it is.
billing_profile_idintThe billing profile assigned.
nameserversstring[]The name servers on record. Empty slots drop out.
capabilitiesobjectWhat the provider supports. Eleven fields: name servers, the transfer lock, the transfer code, child name servers, reading and writing records, signing, e-mail and address forwarding, registrant details and privacy.
addonsobjectWhere the domain add-ons stand. Each carries a state, whether it is free, a price and any invoice waiting.
renewalobjectThe renewal terms. It carries whether it is open, any subscription block, the registry ceiling and the priced term rows.
verificationstringWhere registrant verification stands. Uploading a document happens in the panel alone.
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' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Build the interface FROM THE CAPABILITIES: not every provider does everything, and a closed surface answers 422.
$d = Kernel::internal('client:Domains/GetDomain',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

$tabs = array_keys(array_filter($d['capabilities']));

Changing the Domain Preferences

patch/api/v1/client/domains/{domain}
Domains/UpdateDomain two fields

Changes automatic renewal and the billing profile.

Body 2
auto_renewboolAutomatic renewal for this domain. On a live domain alone, and with a true boolean.
billing_profile_idintThe billing profile id. Zero returns to the account default.
Response fields data — 17
dataobjectThe domain as it now stands. Same shape as the read endpoint.
Errors 7
not_found404No such domain, it is not yours, or access to it is restricted.
nothing_to_update422The body holds no field that can be updated.
auto_renew_invalid422The value is not a boolean.
not_actionable422Automatic renewal was sent while the domain is not live.
autorenew_locked422Account-wide automatic payment is on.
no_auto_pay_source422There is no source to charge.
billing_profile_rejected422A hook refused the profile assignment.
Request
curl -X PATCH 'https://panel.example.com/api/v1/client/domains/example.com' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"auto_renew":true}'
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ auto_renew: true }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// With automatic renewal off a domain expires QUIETLY: setting a reminder is left to you.
Kernel::internal('client:Domains/UpdateDomain',
    ['owner_id' => $uid, 'domain' => $domain, 'auto_renew' => true]);

Raising a Renewal Invoice

post/api/v1/client/domains/{domain}/renew
Domains/RenewDomain it raises an invoice

Raises a renewal invoice for the number of years asked.

Body 1
yearsintreqThe term in years. It has to be one of the priced term rows in the detail.
Response fields data — 8
invoice_idintThe invoice id.
numberstringThe number shown.
statusstringThe raw invoice state.
statestringThe state shown.
totalobjectThe total with tax.
created_atstringThe day it was raised.
paid_atstringThe day it was paid.
due_datestringThe day it falls due.
Errors 4
not_found404No such domain, it is not yours, or access to it is restricted.
not_renewable422The domain cannot be renewed, or a subscription collects it.
years_invalid422The term is none of those offered.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/renew' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"years":2}'
const d = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}`, {
  headers: { Authorization: `Bearer ${clientKey}` },
}).then((r) => r.json());

const terms = d.data.renewal.terms;

await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/renew`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ years: terms[0].years }),
});
$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/renew');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['years' => 2]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The terms offered are bounded by THE REGISTRY CEILING: a ten-year name may take no further two.
$d = Kernel::internal('client:Domains/GetDomain',
    ['owner_id' => $uid, 'domain' => $domain])['data'];

$years = array_column($d['renewal']['terms'], 'years');

Buying a Domain Add-on

post/api/v1/client/domains/{domain}/addons/{key}
Domains/BuyDomainAddon a prorated first period

Buys the name management, privacy or forwarding add-on.

Body
No body is needed. The domain and the add-on key come from the path, and the term is fixed at one year aligned to the domain; send an empty body.
Response fields data — 9
invoice_idintThe invoice to pay.
numberstringThe number shown.
statusstringThe raw invoice state.
statestringThe state shown.
totalobjectThe prorated total of the first period.
created_atstringThe day it was raised.
paid_atstring | nullThe day it was paid. It stays null until the invoice is paid.
due_datestringThe last day to pay.
addonstringThe add-on ordered.
Errors 10
not_found404The add-on key is none of the three values, or the domain was not found.
not_actionable422The domain is not live.
addon_not_supported422The provider offers no such surface.
addon_not_offered422The operator does not sell this add-on on this extension.
addon_free422The add-on is free on this extension. There is nothing to buy.
addon_active422The add-on is already live.
addon_pending422An unpaid add-on invoice is already open.
addon_rejected422A site policy vetoed the purchase. The message carries the reason.
addon_term_invalid422No term is left to prorate, or the amount falls to zero.
addon_failed500The invoice or the record could not be made.
Request
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/addons/whois-privacy' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/addons/${key}`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${clientKey}` },
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// An add-on does not open until THE INVOICE IS PAID: the purchase call raises the document alone.
$inv = Kernel::internal('client:Domains/BuyDomainAddon',
    ['owner_id' => $uid, 'domain' => $domain, 'key' => 'whois-privacy'])['data'];

Kernel::internal('client:Invoices/PayInvoice',
    ['owner_id' => $uid, 'id' => $inv['invoice_id'], 'payment' => ['method' => 'balance']]);

Pitfalls

The capabilities follow the provider

The capability list in the detail carries eleven fields and no provider offers them all. Calling a closed surface comes back with a plain error. Draw the tabs and the buttons from that list, since a fixed interface breaks when the provider changes.

The lock and privacy values are a mirror

The transfer lock and privacy values in the listing and the detail are the last ones saved rather than the provider's current state. Read the real value live from the lock endpoint. On a domain where the mirror was never written the safe assumption is locked.

An add-on does not open until the invoice is paid

Buying an add-on raises an invoice and nothing more; the surface stays closed until it is paid. A second purchase is refused while one waits, and the answer says which invoice blocks it. The first period is prorated to the time left.

The renewal terms are bounded by the registry ceiling

The renewal rows in the detail show the terms the operator prices and that fit under the registry's total ceiling. A domain expiring far out can leave one year in the list, or none. Pick the term from the list rather than guessing.

With automatic renewal off the reminder is yours

A domain with automatic renewal off expires quietly on its due date and enters the recovery window. The API gives no separate warning. Read the expiry from the listing and set a reminder of your own.

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.