# Domain Basics

https://dev.wisecp.com/es/domain-basics

The nine endpoints behind a domain's nameservers, transfer lock, ownership details and recovery.

## Overview

These nine endpoints run a domain's **basics**. Where it points, whether it can be moved, who it appears to belong to, and whether it survives expiry.

All of them make a **real call** to the registry. So they can be slow, they can hit the registry's own rules, and a module may not support an operation at all. Every endpoint shares the same four preconditions. The service must exist, be a domain, have a registrar module attached, and that module must support the operation.

## Reference

### Setting the Nameservers

put/api/v1/admin/services/{id}/domain/nameservers

`Services/SetDomainNameservers` admin goes to the registry

Writes which nameservers the domain points at.

Body 4

ns1stringrequiredThe primary nameserver. It has to be a valid host name.

ns2stringrequiredThe secondary nameserver.

ns3stringA third nameserver.

ns4stringA fourth nameserver.

Response fields data — 2

nameserversstring[]The nameservers written. Ones left empty are not in the list.

idintService id.

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.

ns_required422One of the first two nameservers was empty.

ns_invalid422A nameserver is not a valid host name.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/nameservers' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ns1":"ns1.example.com","ns2":"ns2.example.com"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/nameservers', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    ns1: 'ns1.example.com',
    ns2: 'ns2.example.com',
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/nameservers');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ns1' => 'ns1.example.com',
        'ns2' => 'ns2.example.com',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The list is written WHOLE: leave ns3 and ns4 out and any existing ones are cleared.
$response = Api::Services()->SetDomainNameservers([
    'id'  => 520,
    'ns1' => 'ns1.example.com',
    'ns2' => 'ns2.example.com',
]);
```

### Reading the Transfer Lock

get/api/v1/admin/services/{id}/domain/transfer-lock

`Services/GetDomainTransferLock` admin

Returns whether the domain is locked against being moved to another registrar.

Response fields data — 1

transfer_lockstringThe lock state. The value comes raw from the registry and differs between them.

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Reading returns a RAW value while writing returns 'enabled'/'disabled': not the same vocabulary.
$response = Api::Services()->GetDomainTransferLock(['id' => 520]);
```

### Changing the Transfer Lock

put/api/v1/admin/services/{id}/domain/transfer-lock

`Services/SetDomainTransferLock` admin

Locks the domain against being moved, or unlocks it.

Body 1

statusstringrequired`enable` locks it, `disable` unlocks it.

Response fields data — 2

transfer_lockstringThe lock state afterwards.

idintService id.

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.

invalid_status422The status is neither `enable` nor `disable`.

module_failed500The registry could not complete the operation.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"enable"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'enable' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/transfer-lock');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 'enable']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// While the lock is ON the domain cannot move: unlock it before the client leaves for another registrar.
Api::Services()->SetDomainTransferLock(['id' => 520, 'status' => 'disable']);
$code = Api::Services()->GetDomainAuthCode(['id' => 520]);
```

### Getting the Authorisation Code

get/api/v1/admin/services/{id}/domain/auth-code

`Services/GetDomainAuthCode` admin may go by e-mail

Asks for the code needed to move the domain to another registrar.

Response fields data — 2

auth_codestringThe authorisation code. Present only when the module hands it over directly.

sentboolThe code was e-mailed to the registrant. In that case the code itself is not in the response.

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/services/520/domain/auth-code' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/auth-code', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();

if (body.data.sent) {
  // it went by e-mail, the code is not here
}
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/auth-code');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// There are two response shapes; do NOT assume the code arrived.
$response = Api::Services()->GetDomainAuthCode(['id' => 520])['data'];

$code = $response['auth_code'] ?? null;
$mailed = $response['sent'] ?? false;
```

Response 200 200

```json
{
  "data": {
    "auth_code": "EXAMPLE-AUTH-CODE"
  }
}
```

```json
{
  "data": {
    "sent": true
  }
}
```

### Restoring the Domain

post/api/v1/admin/services/{id}/domain/restore

`Services/RestoreDomain` admin time-bound

Recovers an expired domain. It only works while the recovery window is open.

Body —

——No body is needed; send an empty one. The domain is addressed by the service id in the path.

Response fields data — 2

restoredboolWhether the recovery succeeded.

idintService id.

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.

not_in_window422The domain is not in the recovery window. Once it closes the domain may have been released.

module_failed500The registry could not complete the operation.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/services/520/domain/restore' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/restore', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/restore');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Recovery costs a FEE at the registry, and it has to be called before the window closes.
$response = Api::Services()->RestoreDomain(['id' => 520]);
```

### Checking a Transfer

post/api/v1/admin/services/{id}/domain/check-transfer

`Services/CheckDomainTransfer` admin

Asks the registry where a domain transfer in progress has got to.

Body —

——No body is needed; send an empty one. The transfer is addressed by the service id in the path.

Response fields data — 2

transfer_statusstringThe transfer status. A service that is not a transfer comes back with the error value.

messagestring | nullAn explanation from the registry.

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/services/520/domain/check-transfer' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/check-transfer', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/check-transfer');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The error state comes back inside a 200, not as an HTTP error: read the status before assuming success.
$check = Api::Services()->CheckDomainTransfer(['id' => 520])['data'];

$stuck = $check['transfer_status'] === 'error';
```

### Reading the WHOIS Contacts

get/api/v1/admin/services/{id}/domain/whois

`Services/GetDomainWhois` admin four contacts

Fetches the domain's contact details from the registry.

Response fields data — 4

registrantobject 14 fieldsThe registrant's contact. This is who legally holds the domain.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

administrativeobject 14 fieldsThe administrative contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

technicalobject 14 fieldsThe technical contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

billingobject 14 fieldsThe billing contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/services/520/domain/whois' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/whois', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/whois');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The read goes to the registry and refreshes the local copy too, so it can be slow.
$whois = Api::Services()->GetDomainWhois(['id' => 520])['data'];
$owner = $whois['registrant'];
```

Response 200

```json
{
  "data": {
    "registrant": {
      "first_name": "John",
      "last_name": "Doe",
      "company": "",
      "email": "john@example.com",
      "phone": "5550100",
      "phone_cc": "+1",
      "fax": "",
      "fax_cc": "",
      "address_line1": "123 Market Street",
      "address_line2": "",
      "city": "San Francisco",
      "state": "California",
      "zipcode": "94105",
      "country": "US"
    },
    "administrative": { "first_name": "John", "last_name": "Doe", "email": "john@example.com" },
    "technical": { "first_name": "John", "last_name": "Doe", "email": "john@example.com" },
    "billing": { "first_name": "John", "last_name": "Doe", "email": "john@example.com" }
  }
}
```

### Writing the WHOIS Contacts

put/api/v1/admin/services/{id}/domain/whois

`Services/SetDomainWhois` admin ownership is affected

Writes the contact details to the registry.

Body 4

registrantobject 14 fieldsThe registrant's contact. This is who legally holds the domain.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

administrativeobject 14 fieldsThe administrative contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

technicalobject 14 fieldsThe technical contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

billingobject 14 fieldsThe billing contact.

first_namestringFirst name.

last_namestringLast name.

companystringCompany name.

emailstringE-mail address.

phonestringPhone number.

phone_ccstringCountry code for the phone.

faxstringFax number.

fax_ccstringCountry code for the fax.

address_line1stringFirst line of the address.

address_line2stringSecond line of the address.

citystringCity.

statestringState or region.

zipcodestringPostcode.

countrystringCountry. The two-letter ISO code.

Response fields data — 1

savedboolWhether the save succeeded.

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/whois' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"registrant":{"first_name":"John","last_name":"Doe","email":"john@example.com","phone":"5550100","phone_cc":"+1","address_line1":"123 Market Street","city":"San Francisco","state":"California","zipcode":"94105","country":"US"}}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/whois', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    registrant: {
      first_name: 'John',
      last_name: 'Doe',
      email: 'john@example.com',
      phone: '5550100',
      phone_cc: '+1',
      address_line1: '123 Market Street',
      city: 'San Francisco',
      state: 'California',
      zipcode: '94105',
      country: 'US',
    },
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/whois');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'registrant' => [
            'first_name'    => 'John',
            'last_name'     => 'Doe',
            'email'         => 'john@example.com',
            'address_line1' => '123 Market Street',
            'city'          => 'San Francisco',
            'country'       => 'US',
        ],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Read first and write on top: fields left out can end up empty at the registry.
$whois = Api::Services()->GetDomainWhois(['id' => 520])['data'];

$whois['registrant']['email'] = 'new@example.com';

Api::Services()->SetDomainWhois(['id' => 520] + $whois);
```

### Changing WHOIS Privacy

put/api/v1/admin/services/{id}/domain/whois-privacy

`Services/SetDomainWhoisPrivacy` admin

Turns on or off the hiding of contact details in public lookups.

Body 1

statusstringrequired`enable` hides them, `disable` reveals them.

Response fields data — 1

whois_privacystringThe privacy state afterwards. Either `enabled` or `disabled`.

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.

invalid_status422The status is neither `enable` nor `disable`.

module_failed500The registry could not complete the operation.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/services/520/domain/whois-privacy' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"enable"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/services/520/domain/whois-privacy', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'enable' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/services/520/domain/whois-privacy');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 'enable']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// On most extensions privacy is a PAID add-on, so turning it on can raise an invoice.
$response = Api::Services()->SetDomainWhoisPrivacy([
    'id'     => 520,
    'status' => 'enable',
]);
```

## Pitfalls

> **The authorisation code has two response shapes**
> 
> Some registries hand the code over directly while others e-mail it to the registrant. In the second case the response says only that it was sent and **carries no code**. A client that assumes the code arrived reads an empty value here.

> **The nameserver list is written whole**
> 
> What you send becomes the truth: leave the third and fourth out and any existing ones are **cleared**. Even to change only the primary, the others have to go with it.

> **Changing the registrant changes ownership**
> 
> The WHOIS write is more than updating contact details: the registrant field decides **who legally holds** the domain. On most extensions that change starts an approval process and locks the domain out of transfers for a while. Fields left out can also end up empty at the registry, so read first and write on top.

> **The recovery window closes**
> 
> An expired domain first passes through a period where it can be recovered, then it is released. Once the window closes this endpoint answers `not_in_window` and there is nothing left to do. Recovery also tends to cost a steep fee at the registry.

> **The transfer check returns its error inside a 200**
> 
> A service that is not a transfer, or a transfer that is stuck, shows up in the status field of the response rather than as an HTTP error. A client reading only the status code counts a failed transfer as a success.

## Related Articles

- [DNS Management](https://dev.wisecp.com/en/dns-management)
- [E-mail and URL Forwarding](https://dev.wisecp.com/en/email-and-url-forwarding)
- [Domain Extensions](https://dev.wisecp.com/en/domain-extensions)
