Taking a Domain
The nine endpoints that register, transfer and control the movement of a domain.
Overview
A domain is taken in one of two ways: registering a new one or moving one from another provider. These nine endpoints cover both roads and the transfer controls around them.
Two questions come before an order: what the extension costs and for which terms it sells, and whether the name is free right now. The answer to the second is a snapshot.
The transfer controls work both ways. Handing a domain to someone else means opening the lock and asking for the code; bringing one here means having it opened at the other end and writing the code down here.
Reference
Reading the Extension Prices
Returns the extensions on sale with their terms and prices.
curl 'https://panel.example.com/api/v1/client/domains/tlds?tld=com' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch('https://panel.example.com/api/v1/client/domains/tlds?tld=com', {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const years = Object.keys(data[0].register_years ?? {});$ch = curl_init('https://panel.example.com/api/v1/client/domains/tlds?tld=com');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The price per term comes ONLY when one extension is asked for: the general listing holds no such table.
$one = Kernel::internal('client:Domains/GetTlds', ['owner_id' => $uid, 'tld' => 'com'])['data'][0];
$priced = array_keys($one['register_years'] ?? []);Asking Whether a Name Is Free
Asks live whether a domain can be taken.
curl -X POST 'https://panel.example.com/api/v1/client/domains/check' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"domain":"example-shop.com"}'const res = await fetch('https://panel.example.com/api/v1/client/domains/check', {
method: 'POST',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ domain }),
});
const { data } = await res.json();
if (data.premium) showPremiumPrice(data.premium_price);$ch = curl_init('https://panel.example.com/api/v1/client/domains/check');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['domain' => $name]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Do not read UNKNOWN as free: the registrar could not be reached and the order can still fail.
$r = Kernel::internal('client:Domains/CheckDomain',
['owner_id' => $uid, 'domain' => $name])['data'];
$safe = $r['status'] === 'available';Registering a Domain
Orders a new domain and collects payment.
curl -X POST 'https://panel.example.com/api/v1/client/domains/register' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"terms":true,"domain":"example-shop.com","years":1,"payment":{"method":"balance"}}'const res = await fetch('https://panel.example.com/api/v1/client/domains/register', {
method: 'POST',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
terms: true,
domain,
years: 1,
whois_profile_id: profileId,
payment: { method: 'balance' },
}),
});
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/domains/register');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($order),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A SPECIALLY PRICED name registers for ONE YEAR whatever is sent, and it wants the acceptance flag.
$c = Kernel::internal('client:Domains/CheckDomain',
['owner_id' => $uid, 'domain' => $name])['data'];
Kernel::internal('client:Domains/RegisterDomain', ['owner_id' => $uid, 'terms' => true,
'domain' => $name, 'accept_premium' => $c['premium'],
'payment' => ['method' => 'balance']]);Transferring a Domain
Moves a domain held at another provider over here.
curl -X POST 'https://panel.example.com/api/v1/client/domains/transfer' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"terms":true,"domain":"example.com","auth_code":"EPP-CODE","payment":{"method":"balance"}}'const res = await fetch('https://panel.example.com/api/v1/client/domains/transfer', {
method: 'POST',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ terms: true, domain, auth_code: code, payment }),
});
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/domains/transfer');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($order),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A transfer is ALWAYS one year and does not finish at payment: follow it on the transfer status endpoint.
$o = Kernel::internal('client:Domains/TransferDomain', ['owner_id' => $uid] + $order)['data'];
$sid = $o['domain']['service_id'];Reading the Transfer Lock
Reads from the provider whether the domain is closed to transfer.
curl 'https://panel.example.com/api/v1/client/domains/example.com/transfer-lock' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/transfer-lock`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
if (! data.live) warnStaleValue();$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/transfer-lock');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Where the live flag is false the value can be old: weigh the lock decision against it.
$l = Kernel::internal('client:Domains/GetTransferLock',
['owner_id' => $uid, 'domain' => $domain])['data'];
$trusted = $l['live'];Changing the Transfer Lock
Closes the domain to transfer or opens it.
curl -X PUT 'https://panel.example.com/api/v1/client/domains/example.com/transfer-lock' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"locked":false}'const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/transfer-lock`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ locked: false }),
});
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/transfer-lock');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['locked' => false]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// OPENING the lock leaves the domain open to being taken: close it again as soon as the move is done.
Kernel::internal('client:Domains/UpdateTransferLock',
['owner_id' => $uid, 'domain' => $domain, 'locked' => false]);Asking for the Transfer Code
Sends the transfer code to the domain owner's e-mail.
curl -X POST 'https://panel.example.com/api/v1/client/domains/example.com/auth-code' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/auth-code`, {
method: 'POST',
headers: { Authorization: `Bearer ${clientKey}` },
});
if (res.ok) tellUserToCheckEmail();$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/auth-code');
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);// The code NEVER comes back in the answer: it goes to the owner's e-mail, which is a deliberate safeguard.
Kernel::internal('client:Domains/SendAuthCode', ['owner_id' => $uid, 'domain' => $domain]);Saving an Incoming Transfer Code
Updates the code for a transfer under way.
curl -X PUT 'https://panel.example.com/api/v1/client/domains/example.com/auth-code' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"code":"NEW-EPP-CODE"}'const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/auth-code`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ code }),
});
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/auth-code');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['code' => $code]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// This endpoint SAVES and does not restart the transfer: a changed code still wants a fresh attempt at the provider.
Kernel::internal('client:Domains/SaveAuthCode',
['owner_id' => $uid, 'domain' => $domain, 'code' => $code]);Asking Where a Transfer Is
Reads from the provider where a transfer under way stands.
curl 'https://panel.example.com/api/v1/client/domains/example.com/transfer-status' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/domains/${domain}/transfer-status`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
showProgress(data.transfer.state, data.transfer.message);$ch = curl_init('https://panel.example.com/api/v1/client/domains/' . $domain . '/transfer-status');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Every call goes LIVE to the provider: do not poll it often in a watcher, hourly is plenty.
$t = Kernel::internal('client:Domains/GetTransferStatus',
['owner_id' => $uid, 'domain' => $domain])['data'];
$done = $t['transfer']['state'] === 'completed';Pitfalls
The availability check gives three answers and unknown is one of them: the registrar could not be reached. Treating it as free and ordering leads to a failure at registration. Trust a plain free answer alone.
On a name the registry prices apart the term field is ignored and the registration runs one year. Such a name also wants the flag accepting the special price, and the order is refused without it. Read the price from the availability answer.
The transfer lock stops your domain being moved without permission. Opening it takes that protection away for as long as it stays open. Close it again as soon as the move is done, since nobody does it for you.
The endpoint asking for a code says sent and nothing more; the code goes to the domain owner's e-mail. That is a deliberate safeguard: a stolen API key cannot move a domain in one call. Do not try to show the code in an interface.
The lock read carries a live flag. Where the provider offers no reader or the read fails, the last known value comes back and it may not match reality. Check that flag in any flow that decides on the lock.
Paying for a transfer order is where the work begins: the domain waits on approval at the other end and that can take days. The term is one year either way. Follow it on the transfer status endpoint, and ask rarely since every call reaches the provider.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.