Domain Pricing and Settings

7 views Markdown

The seven endpoints behind the overall rules of selling domains: recovery, add-on fees, automatic pricing and premium.

Overview

These endpoints look not at one extension but at the overall rules of selling domains: how long and at what cost an expired domain can be recovered, what DNS management or WHOIS privacy costs, whether prices are set automatically, and whether premium domains are sold at all.

An extension's own price and features live elsewhere; the settings here work above them. The one exception is the recovery fee: name an extension and it is written for that one only.

Reference

A WHOIS Query

get/api/v1/admin/products/domain/whois
Products/GetDomainWhois admin goes outside

Runs a WHOIS query for a domain and returns the raw output.

Query parameters 1
domainstringrequiredThe domain to look up, extension included.
Response fields data — 3
domainstringThe domain that was looked up.
availableboolWhether the domain is free.
outputstringThe raw text from the registry. The format differs between registries; do not rely on parsing it.
Errors 4
domain_required422domain was empty.
invalid_domain422The domain could not be read.
whois_failed422The WHOIS data could not be fetched. The remote server may be unreachable or rate limiting you.
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/products/domain/whois' \
  -H "Authorization: Bearer $API_KEY" \
  -d domain=example.com
const url = new URL('https://panel.example.com/api/v1/admin/products/domain/whois');
url.searchParams.set('domain', 'example.com');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/products/domain/whois?' . http_build_query(['domain' => 'example.com']);

$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);
// The query goes out to a remote server: it can be slow and it can be rate limited.
$response = Api::Products()->GetDomainWhois([], ['domain' => 'example.com']);

$free = $response['data']['available'] ?? false;

Grace and Redemption Fees

put/api/v1/admin/products/domain/grace-redemption
Products/SetDomainGraceRedemption admin global or per extension

Sets how long an expired domain can be recovered and what that costs.

Body 3
extensionstringThe extension. Left empty, the setting becomes the default for every extension.
graceobjectThe grace period: its length and a fee per currency. The window in which the domain can be taken back before it is released.
durationintHow many days the period lasts.
feesobjectA map from currency code to a fee object.
feefloatThe fee amount.
statusboolWhether the fee applies. Switched off, the amount is stored but not charged.
redemptionobjectThe redemption period: its length and a fee per currency. The window after grace ends, usually an expensive one.
durationintHow many days the period lasts.
feesobjectA map from currency code to a fee object.
feefloatThe fee amount.
statusboolWhether the fee applies. Switched off, the amount is stored but not charged.
Response fields data — 1
extensionstring | nullThe extension the setting was applied to. Empty on the global default.
Errors 2
not_found404The extension you gave does not exist.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/products/domain/grace-redemption' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"grace":{"duration":30,"fees":{"USD":{"fee":0,"status":false}}},"redemption":{"duration":30,"fees":{"USD":{"fee":80,"status":true}}}}'
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/grace-redemption', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    grace: { duration: 30, fees: { USD: { fee: 0, status: false } } },
    redemption: { duration: 30, fees: { USD: { fee: 80, status: true } } },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/grace-redemption');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'redemption' => [
            'duration' => 30,
            'fees'     => ['USD' => ['fee' => 80, 'status' => true]],
        ],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Giving an extension writes the setting for THAT one only; the global default stays.
Api::Products()->SetDomainGraceRedemption([
    'extension'  => 'com',
    'redemption' => [
        'duration' => 30,
        'fees'     => ['USD' => ['fee' => 80, 'status' => true]],
    ],
]);

Domain Add-on Fees

put/api/v1/admin/products/domain/addon-pricing
Products/SetDomainAddonPricing admin every extension

Sets the fee for DNS management, forwarding, WHOIS privacy and the transfer code.

Body 2
typestringrequiredThe feature to price: dns_manage, forwarding, whois_privacy or epp_code.
feesobjectrequiredA map from currency code to a fee object.
feefloatThe fee amount.
statusboolWhether the fee applies. Switched off, the amount is stored but not charged.
Response fields data — 1
typestringThe feature whose fee was written.
Errors 2
invalid_type422The feature is not one of the four values.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/products/domain/addon-pricing' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"whois_privacy","fees":{"USD":{"fee":5,"status":true}}}'
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/addon-pricing', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'whois_privacy',
    fees: { USD: { fee: 5, status: true } },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/addon-pricing');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type' => 'whois_privacy',
        'fees' => ['USD' => ['fee' => 5, 'status' => true]],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The fee is the same on every extension; which extension OFFERS it lives on the TLD record.
Api::Products()->SetDomainAddonPricing([
    'type' => 'whois_privacy',
    'fees' => ['USD' => ['fee' => 5, 'status' => true]],
]);

The Automatic Pricing State

get/api/v1/admin/products/domain/auto-pricing
Products/GetDomainAutoPricing admin

Returns whether automatic pricing is on and what the profit margin is.

Response fields data — 2
statusboolWhether automatic pricing is on.
profit_ratefloatThe profit margin added on top of cost, as a percentage.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/products/domain/auto-pricing' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/products/domain/auto-pricing', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/auto-pricing');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Products()->GetDomainAutoPricing();

Setting Automatic Pricing

put/api/v1/admin/products/domain/auto-pricing
Products/SetDomainAutoPricing admin two branches

Turns automatic pricing on or off, or changes the margin and reprices.

Body 3
updatestringrequiredWhich branch runs: status or pricing.
statusboolTurns automatic pricing on or off. Only on the status branch.
ratefloatThe profit margin as a percentage. Only on the pricing branch; writing it reprices every auto-priced extension at once.
Response fields data — 2
dataobjectThe automatic pricing settings as they now stand. Same shape as the read endpoint.
Errors 3
invalid_update422update is neither of the two values.
update_failed422The setting could not be stored.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/products/domain/auto-pricing' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"update":"pricing","rate":20}'
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/auto-pricing', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ update: 'pricing', rate: 20 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/auto-pricing');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['update' => 'pricing', 'rate' => 20]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Writing the margin reprices EVERY auto-priced extension there and then.
Api::Products()->SetDomainAutoPricing([
    'update' => 'pricing',
    'rate'   => 20,
]);

Premium Domain Settings

get/api/v1/admin/products/domain/premium
Products/GetDomainPremium admin

Returns whether premium domain sales are on and what the price tiers are.

Response fields data — 2
statusboolWhether premium domain sales are on.
pricingobject[]The price tiers.
amountfloatThe amount the tier starts at.
markupfloatThe markup applied in that tier.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/products/domain/premium' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/products/domain/premium', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/premium');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Products()->GetDomainPremium();

Writing the Premium Settings

put/api/v1/admin/products/domain/premium
Products/SetDomainPremium admin two branches

Turns premium sales on or off, or writes the price tiers.

Body 3
updatestringrequiredWhich branch runs: status or pricing.
statusboolTurns premium sales on or off. Only on the status branch.
feesobject[]The price tiers. Only on the pricing branch; the list is written whole.
amountfloatThe amount the tier starts at.
markupfloatThe markup applied in that tier.
Response fields data — 2
dataobjectThe premium settings as they now stand. Same shape as the read endpoint.
Errors 2
invalid_update422update is neither of the two values.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/products/domain/premium' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"update":"pricing","fees":[{"amount":100,"markup":10},{"amount":1000,"markup":5}]}'
const res = await fetch('https://panel.example.com/api/v1/admin/products/domain/premium', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    update: 'pricing',
    fees: [
      { amount: 100, markup: 10 },
      { amount: 1000, markup: 5 },
    ],
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/products/domain/premium');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'update' => 'pricing',
        'fees'   => [
            ['amount' => 100,  'markup' => 10],
            ['amount' => 1000, 'markup' => 5],
        ],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The tier list is written whole: a missing tier is deleted.
Api::Products()->SetDomainPremium([
    'update' => 'pricing',
    'fees'   => [
        ['amount' => 100,  'markup' => 10],
        ['amount' => 1000, 'markup' => 5],
    ],
]);

Pitfalls

Writing the margin changes prices right away

Writing the profit margin does not only store a setting: every extension with automatic pricing on is repriced at the same moment. The change reaches the storefront immediately, with no cron to wait for. Move the margin in small steps and read the result from the extension detail.

Two endpoints pick a single branch

The automatic pricing and premium endpoints run a single branch chosen by update. A field sent on the wrong branch is ignored without a word: sending the margin on the status branch changes nothing and raises no error. Always confirm through the read endpoint.

The fee and the offer live apart

The add-on fee is written here, shared by every extension. Whether an extension offers that feature at all sits on its own record. A feature you priced but no extension offers is never paid for, so check both together.

The WHOIS query goes outside

The query goes to a remote server: it can be slow, it can hit a rate limit, and the text that comes back is formatted differently by every registry. If you are writing a bulk check, set a timeout and use the available field rather than parsing the raw output.

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.