# Domain Extensions

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

The ten endpoints that manage the domain extensions you sell, their prices and their registration documents.

## Overview

These endpoints run the domain extensions you sell: which ones are open, which registrar they are attached to, what they cost, and which documents are asked for at registration.

An extension appears in the path **by its own name**, not by an id. The price table has three levels: operation type, then year, then currency. The same extension can be priced one way for a five-year registration and another for a one-year renewal.

## Reference

### Listing the Extensions

get/api/v1/admin/products/domain/tlds

`Products/GetDomainTlds` admin paged

Returns the domain extensions the installation offers.

Query parameters 3

searchstringSearches the extension.

pageintDefaults to 1.

limitintDefaults to 25, maximum 100.

Response fields data[] — 7

idintId of the extension record.

extensionstringThe extension itself. This is the path segment on the other endpoints, not the id.

statusstring`active` or `inactive`.

rankintThe display order.

modulestringThe registrar module attached. `none` when there is none, and then registration cannot run automatically.

auto_pricingboolWhether automatic pricing is on.

featuresobject 4 fieldsWhat the extension supports.

dns_manageboolThe client can manage DNS records.

forwardingboolDomain forwarding is available.

whois_privacyboolThe registrant details can be hidden.

epp_codeboolA transfer code can be obtained.

Meta 4

totalintTotal records matching the filter.

pageintThe page you are on.

limitintThe page size.

next_pageintThe next page. Zero means you are on the last one.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -G 'https://panel.example.com/api/v1/admin/products/domain/tlds' \
  -H "Authorization: Bearer $API_KEY" \
  -d limit=100
```

```javascript
const url = new URL('https://panel.example.com/api/v1/admin/products/domain/tlds');
url.searchParams.set('limit', '100');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
```

```php
$url = 'https://panel.example.com/api/v1/admin/products/domain/tlds?' . http_build_query(['limit' => 100]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

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

```php
$response = Api::Products()->GetDomainTlds([], ['limit' => 100]);
```

### Extension Detail

get/api/v1/admin/products/domain/tlds/{tld}

`Products/GetDomainTld` admin addressed by extension

Returns one extension with its costs and the whole price table.

Response fields data — 13

idintId of the extension record.

extensionstringThe extension itself.

statusstring`active` or `inactive`.

rankintThe display order.

modulestringThe registrar module attached.

auto_pricingboolWhether automatic pricing is on.

featuresobject 4 fieldsWhat the extension supports.

dns_manageboolThe client can manage DNS records.

forwardingboolDomain forwarding is available.

whois_privacyboolThe registrant details can be hidden.

epp_codeboolA transfer code can be obtained.

min_yearsintThe shortest registration in years.

max_yearsintThe longest registration in years.

register_costfloatWhat registration costs you at the registrar.

renewal_costfloatWhat renewal costs you at the registrar.

transfer_costfloatWhat a transfer costs you at the registrar.

pricingobjectA three-level map: operation type, then year, then currency code. The innermost object carries `amount`, `status`, `promotion` and `promotion_status`.

Errors 2

not_found404No such extension.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

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

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

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

```php
$response = Api::Products()->GetDomainTld(['tld' => 'com']);

// The price table has three levels: type, year, currency.
$oneYear = $response['data']['pricing']['register']['1']['USD']['amount'] ?? null;
```

Response 200

```json
{
  "data": {
    "id": 3,
    "extension": "com",
    "status": "active",
    "module": "Enom",
    "auto_pricing": false,
    "features": {
      "dns_manage": true,
      "forwarding": false,
      "whois_privacy": true,
      "epp_code": true
    },
    "min_years": 1,
    "max_years": 10,
    "register_cost": 8.5,
    "renewal_cost": 9,
    "transfer_cost": 8.5,
    "pricing": {
      "register": {
        "1": {
          "USD": {
            "amount": 12,
            "status": true,
            "promotion": 0,
            "promotion_status": false
          }
        }
      }
    }
  }
}
```

### Adding an Extension

post/api/v1/admin/products/domain/tlds

`Products/CreateDomainTld` admin 201

Adds a new extension. If you like, the starting prices and the logo go in the same request.

Body 7

extensionstringrequiredThe extension. A leading dot is dropped.

registrarstringName of the registrar module. Defaults to none attached.

statusstring`active` or `inactive`.

display_orderintThe display order.

featuresobject 4 fieldsWhat the extension supports.

dns_manageboolThe client can manage DNS records.

forwardingboolDomain forwarding is available.

whois_privacyboolThe registrant details can be hidden.

epp_codeboolA transfer code can be obtained.

pricingobjectA map from currency code to a `{register, transfer, renewal}` object. There is no year breakdown here; the detailed table goes in through the pricing endpoint.

logostringThe extension logo. A base64 data URI or a link that can be fetched.

Response fields data — 13

dataobjectThe extension created. Same shape as the detail endpoint, but a fresh extension holds no prices yet, so `pricing` comes back empty.

Errors 4

extension_required422`extension` was empty.

extension_exists422This extension already exists.

create_failed422The insert failed.

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/products/domain/tlds' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"extension":"com","registrar":"Enom","status":"active","features":{"dns_manage":true,"whois_privacy":true},"pricing":{"USD":{"register":12,"transfer":12,"renewal":14}}}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/tlds', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    extension: 'com',
    registrar: 'Enom',
    status: 'active',
    features: { dns_manage: true, whois_privacy: true },
    pricing: { USD: { register: 12, transfer: 12, renewal: 14 } },
  }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/tlds');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'extension' => 'com',
        'registrar' => 'Enom',
        'status'    => 'active',
        'features'  => ['dns_manage' => true, 'whois_privacy' => true],
        'pricing'   => ['USD' => ['register' => 12, 'transfer' => 12, 'renewal' => 14]],
    ]),
]);

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

```php
// The price here is a one-year starting point; use the pricing endpoint for the year breakdown.
$response = Api::Products()->CreateDomainTld([
    'extension' => 'com',
    'registrar' => 'Enom',
    'pricing'   => ['USD' => ['register' => 12, 'renewal' => 14]],
]);
```

### Deleting an Extension

delete/api/v1/admin/products/domain/tlds/{tld}

`Products/DeleteDomainTld` admin prices go too

Deletes the extension and its prices.

Response fields data — 2

deletedboolWhether the delete succeeded.

extensionstringThe extension that was deleted.

Errors 3

not_found404No such extension.

blocked_by_gate422The `gate:domain.tld_delete` hook vetoed the operation.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/domain/tlds/com' \
  -H "Authorization: Bearer $API_KEY"
```

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

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

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

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

```php
$response = Api::Products()->DeleteDomainTld(['tld' => 'com']);
```

### Bulk Action

post/api/v1/admin/products/domain/tlds/bulk

`Products/BulkDomainTlds` admin two different bodies

Switches several extensions on or off, deletes them, or changes their settings.

Body 3

actionstringrequiredThe action: `enable`, `disable`, `delete` or `change`.

extensionsstring[]The extensions to touch. Needed by the first three actions.

changesobject[]A per-extension list of changes. Each element carries `extension` and the fields to change: the features, the module, the status, the order. Only on the `change` action.

Response fields data — 2

actionstringThe action that was applied.

appliedstring[]The extensions the action actually reached.

Errors 1

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/products/domain/tlds/bulk' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"action":"disable","extensions":["net","org"]}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/tlds/bulk', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ action: 'disable', extensions: ['net', 'org'] }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/tlds/bulk');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'action'     => 'disable',
        'extensions' => ['net', 'org'],
    ]),
]);

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

```php
// The 'change' action wants a list of changes, not a list of extensions.
$response = Api::Products()->BulkDomainTlds([
    'action'  => 'change',
    'changes' => [
        ['extension' => 'net', 'whois_privacy' => true],
        ['extension' => 'org', 'status' => 'inactive'],
    ],
]);
```

### Setting the Pricing

put/api/v1/admin/products/domain/tlds/{tld}/pricing

`Products/SetDomainTldPricing` admin three-level table

Writes the extension's registration, transfer and renewal prices, broken down by year and currency.

Body 2

auto_pricingboolTurns automatic pricing on or off.

pricingobjectrequiredA three-level map: operation type (`register`, `transfer`, `renewal`), then year, then currency code. The innermost object carries `status`, `fee`, `promotion_status` and `promotion`.

Response fields data — 13

dataobjectThe extension as it now stands. Same shape as the detail endpoint: prices come back nested type, year, currency, so read the whole `pricing` map rather than assuming the shape you sent survived.

Errors 3

not_found404No such extension.

update_failed422The pricing could not be stored.

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/products/domain/tlds/com/pricing' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"auto_pricing":false,"pricing":{"register":{"1":{"USD":{"status":true,"fee":12,"promotion_status":false,"promotion":0}}}}}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/tlds/com/pricing', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    auto_pricing: false,
    pricing: {
      register: {
        1: { USD: { status: true, fee: 12, promotion_status: false, promotion: 0 } },
      },
    },
  }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/tlds/com/pricing');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'auto_pricing' => false,
        'pricing'      => [
            'register' => [
                1 => ['USD' => ['status' => true, 'fee' => 12]],
            ],
        ],
    ]),
]);

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

```php
// Reading calls the field 'amount', writing calls it 'fee' - they hold the same value.
$current = Api::Products()->GetDomainTld(['tld' => 'com'])['data']['pricing'];
$amount  = $current['register']['1']['USD']['amount'];

Api::Products()->SetDomainTldPricing([
    'tld'     => 'com',
    'pricing' => [
        'register' => [1 => ['USD' => ['status' => true, 'fee' => $amount + 1]]],
    ],
]);
```

### Extensions That Ask for Documents

get/api/v1/admin/products/domain/docs

`Products/GetDomainDocsList` admin

Returns the extensions that ask for documents at registration.

Query parameters 1

searchstringSearches the extension.

Response fields data[] — 1

tldstringThe extension itself.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

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

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

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

```php
$response = Api::Products()->GetDomainDocsList();
```

### Reading the Document Set

get/api/v1/admin/products/domain/tlds/{tld}/docs

`Products/GetDomainTldDocs` admin

Returns the documents an extension asks for at registration.

Response fields data — 3

tldstringThe extension itself.

descriptionobjectA map from language code to description. Shown to the client above the document set.

docsobject[] 6 fieldsThe documents asked for.

idintDocument id.

typestring`text`, `file` or `select`.

sortnumintThe sort number.

statusstring`active` or `inactive`.

namesobjectA map from language code to document name.

optionsarrayThe values offered on the choice type.

Errors 2

not_found404No such extension.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs' \
  -H "Authorization: Bearer $API_KEY"
```

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

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

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

```php
$response = Api::Products()->GetDomainTldDocs(['tld' => 'us']);
```

### Writing the Document Set

put/api/v1/admin/products/domain/tlds/{tld}/docs

`Products/SetDomainTldDocs` admin full replacement

Writes the document set. What you send becomes the truth; a document missing from it is deleted.

Body 2

descriptionobjectA map from language code to description.

docsobjectrequiredA map from document key to a document object. Each carries a type, a name per language, and rules for the type: allowed extensions, maximum size, choice values. Give a new document a key you made up; for an existing one use the record's id.

Response fields data — 3

dataobjectThe document set as it now stands. Same shape as the read endpoint, so the keys you made up come back as record ids.

Errors 3

not_found404No such extension.

update_failed422The set could not be stored. A document name may be empty, an extension may be duplicated, or nothing may have changed.

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/products/domain/tlds/us/docs' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"description":{"en":"Provide a valid ID."},"docs":{"n1":{"type":"text","name":{"en":"Registrant ID"}}}}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    description: { en: 'Provide a valid ID.' },
    docs: {
      n1: { type: 'text', name: { en: 'Registrant ID' } },
    },
  }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'description' => ['en' => 'Provide a valid ID.'],
        'docs'        => [
            'n1' => ['type' => 'text', 'name' => ['en' => 'Registrant ID']],
        ],
    ]),
]);

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

```php
// To ADD a document, send the existing set too, or the earlier ones are deleted.
$existing = Api::Products()->GetDomainTldDocs(['tld' => 'us'])['data']['docs'];

$docs = [];
foreach ($existing as $doc) $docs[$doc['id']] = ['type' => $doc['type'], 'name' => $doc['names']];
$docs['n1'] = ['type' => 'file', 'name' => ['en' => 'Passport scan']];

Api::Products()->SetDomainTldDocs(['tld' => 'us', 'docs' => $docs]);
```

### Deleting the Document Set

delete/api/v1/admin/products/domain/tlds/{tld}/docs

`Products/DeleteDomainTldDocs` admin

Deletes the extension's whole document set.

Response fields data — 2

deletedboolWhether the delete succeeded.

tldstringThe extension itself.

Errors 2

not_found404There is no document set for this extension.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/tlds/us/docs');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

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

```php
$response = Api::Products()->DeleteDomainTldDocs(['tld' => 'us']);
```

## Pitfalls

> **Reading says amount, writing says fee**
> 
> In the price table the same value is called `amount` when you read it and `fee` when you write it. That means the table you read from the detail cannot go straight back: you have to rename the field first.

> **The document set is replaced in full**
> 
> The document write does not merge. To add one document you have to send the whole existing set as well, or the earlier ones are deleted and that extension stops asking for anything. Give a new document a key you made up, and use the record's id for an existing one.

> **The bulk endpoint expects two different bodies**
> 
> Enabling, disabling and deleting want a list of extensions, while changing settings wants a per-extension list of changes. Sending the wrong field quietly leaves the action empty: the returned list comes back empty and no error is raised. Check what was applied from that list.

> **An extension without a module runs by hand**
> 
> With no registrar module attached the extension can still be sold, but registration, transfer and renewal do not run automatically; an operator does them by hand. Check the module is attached before opening the extension.

## Related Articles

- [Domain Pricing and Settings](https://dev.wisecp.com/en/domain-pricing-settings)
- [Product Endpoints](https://dev.wisecp.com/en/product-endpoints)
