Registering is three calls: check the name, register it, then read its state until the registry has finished. The same pattern covers transfers, renewals and restores.
Check availability
Ask whether the name is free and what it costs you. Nothing is reserved or charged.
Check a domain name #
GET
/reseller/domains/check
Tells you whether name can be registered, with your register and
transfer price for the TLD’s minimum term. Use it before
POST /reseller/domains/register or /transfer.
available is null when the registry could not be asked right now:
retry later. A name already held on this server is reported as not
available. Nothing is reserved or charged.
A definite answer is reused for up to 2 minutes, so asking again for
the same name costs no registry lookup; register and transfer always
check afresh before charging.
- Authentication: bearer Token
- Rate limit: 300 req/min per account
Parameters
Query
-
namestring required-
Max length
255
-
Max length
Responses
-
200Availability and price.
Response body
application/json-
dataobject6 properties
-
namestring -
availableboolean | null -
currencystring -
register_pricenumber | null -
transfer_pricenumber | null -
min_yearsinteger
-
-
-
401unauthenticatedApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundApiError -
422invalid_requestinvalid_domaintld_not_sellableApiError -
429rate_limitedApiError
cURL
curl 'https://portal.supporthost.com/api/v1/reseller/domains/check?name=example.com' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/check?name=example.com');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
// Save as a .mjs file (ES module, Node 18+): it uses top-level await.
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/check?name=example.com', {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
},
});
console.log(await response.json());
200
{
"data": {
"name": "example.com",
"available": true,
"currency": "EUR",
"register_price": 11.1699999999999999289457264239899814128875732421875,
"transfer_price": 11.1699999999999999289457264239899814128875732421875,
"min_years": 1
}
}
Error responses share one format: see Errors.
available is null when the registry could not be asked right now: retry later rather than treating it as taken. The check counts towards the reseller reads limit (300 calls per minute, shared by your whole account), so do not call it on every keystroke of a search box.
Register the domain
Send the name, the owner contact (registrant) and, optionally, the term in years and the nameservers. The price is debited from your credit before the registry is contacted, and refunded automatically if the registry refuses. Send an Idempotency-Key so a network retry can never register or charge twice (see Idempotency).
Register a domain #
POST
/reseller/domains/register
Registers an available name, paid from your credit at your net price.
Check availability and price first with GET /reseller/domains/check.
yearsdefaults to the TLD’s minimum term;GET /reseller/pricinglists the terms you can buy.nameservers(2–5 host names) is optional: omitted, the default nameservers configured on this server are used.registrantis the owner contact. The phone is+CC.NNNN(e.g.+39.0212345678), or a local number plusphone_country_code(ISO country, e.g.IT).extra_fieldsis an object of string values for the registry-specific data some TLDs require (entity type, tax codes, language…). Which keys are required depends on the TLD and on this server’s configuration, and some are only required depending on another field’s answer. Send a checkbox as"1". Keys the TLD does not declare are ignored. A missing or invalid field answers422 invalid_contact, whosefieldsmap names every field to fix.sourceis optional: your platform’s own additional fields, as your platform holds them, for this server to translate intoextra_fields. A key you send inextra_fieldswins over a translated one.
Side effects: the charge is taken from your credit before the registry
is contacted, and refunded automatically if the registry refuses. On
424 registrar_unknown the registry did not confirm the outcome and the
charge is NOT refunded while it is reconciled: do not retry with a new
Idempotency-Key.
- Authentication: bearer Token
- Rate limit: 30 req/min per account
Parameters
Header
-
Idempotency-KeystringOptional. 1–255 printable ASCII characters, scoped to your account. A retry with the same key and the same request returns the stored response byte for byte (withIdempotent-Replayed: true) and never charges twice. The same key with a different request answers422 idempotency_key_reused; while the first request is still running,409 idempotency_in_progress(retry afterretry_afterseconds). Keys are kept for 30 days. See Idempotency in the introduction.-
Min length
1 -
Max length
255
-
Min length
Body application/json
required
-
namestring requiredThe domain to register, e.g.example.com. A Unicode (IDN) name is accepted only on a TLD that supports it, and is converted to its ASCII form; otherwise422 invalid_domain.-
Max length
255
-
Max length
-
yearsintegerRegistration term. Defaults to the TLD’s minimum; must be one of theregistertermsGET /reseller/pricinglists for the TLD, otherwise422 invalid_years(withallowed_years).-
Minimum
1
-
Minimum
-
nameserversarray of string2 to 5 nameserver host names. Optional: when omitted, the default nameservers configured on this server are used.-
Min items
2 -
Max items
5 -
Pattern
^[A-Za-z0-9.\-]+$ -
Max length
255
-
Min items
-
registrantobject requiredThe owner contact of the domain, the only contact these calls take. The registrar fills the other roles the TLD needs (admin, tech, billing) on its own: most copy the registrant, some use contacts of their own. Change any role afterwards withPUT /reseller/domains/{name}/contacts.11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | nullCompany name. Some TLDs require it and some refuse it: either case answers422 invalid_contact.-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring requiredInternational format+CC.NUMBER, e.g.+39.0212345678. You may instead send a local number here together withregistrant.phone_country_code(ISO country, e.g.IT).-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring requiredISO 3166-1 alpha-2 country code, e.g.IT. When the TLD listsregistrant_countriesinGET /reseller/pricing, it must be one of them, otherwise422 invalid_contactbefore any credit is taken.-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
extra_fieldsmap of stringRegistry-specific data some TLDs require (e.g. entity type, tax codes, language). Which keys are required depends on the TLD and on this server’s configuration, and some are only required depending on the answer to another field. For a new registration or transfer,GET /reseller/tlds/{tld}/extra-fieldslists them, with their conditions; for an existing domain,GET /reseller/domains/{name}/contactsreturns the ones it uses. Values are strings: send a checkbox as"1". Keys the TLD does not declare are ignored. A missing or invalid field answers422 invalid_contactwith afieldsmap naming each field to fix. -
sourceobjectOptional. The additional domain fields of your own platform, sent as your platform holds them, for this server to translate intoextra_fields. A registrar module generated by this server fills it in: you do not need it when you sendextra_fieldsyourself. A key you send inextra_fieldsalways wins over a translated one.5 properties
-
platformstringYour platform, lower case:whmcs,hostbill.-
Pattern
^[a-z][a-z0-9-]*$ -
Max length
32
-
Pattern
-
versionstring | nullYour platform’s own version, e.g.8.13.1: field names differ between versions.-
Max length
50
-
Max length
-
module_versionstring | nullVersion of the registrar module sending the request.-
Max length
50
-
Max length
-
fieldsmap of stringThe platform’s additional fields, name => value, exactly as the platform holds them (e.g."Legal Type": "Companies/one man companies"). At most 50, values up to 255 characters. -
translatedarray of stringTheextra_fieldskeys your module translated itself fromsource.fields. This server’s own translation wins over them; keys not listed here are yours and win over it.-
Max items
100 -
Max length
64
-
Max items
-
Responses
-
200Registered.
Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject3 properties
-
statestring-
Value
completed
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
-
202Accepted — completes laterAccepted but not completed yet: the registry has taken the request and will finish it later. Your credit has already been debited; the charges stay
pendinguntil the operation completes (then they are billed on your next monthly invoice) and are refunded automatically if it fails. PollGET /reseller/domains/{name}untilstatusleavespending_registration/pending_transfer(a failed transfer showstransfer.failed: true).Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject3 properties
-
statestring-
Value
pending
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
-
401unauthenticatedApiError -
402insufficient_creditApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundApiError -
409operation_in_progressidempotency_in_progressApiError -
422invalid_requestinvalid_contactinvalid_domaintld_not_sellableinvalid_yearsdomain_unavailableregistration_data_missingregistrar_errorinvalid_idempotency_keyidempotency_key_reusedApiError -
424registrar_unknownApiError -
429rate_limitedApiError -
500operation_failedApiError -
503whois_unavailableApiError
cURL
# Idempotency-Key is optional: a retry with the same key is never processed twice.
curl -X POST 'https://portal.supporthost.com/api/v1/reseller/domains/register' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 00000000-0000-4000-8000-000000000000' \
-d '{
"name": "example.com",
"years": 1,
"nameservers": [
"ns1.example.net",
"ns2.example.net"
],
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/register');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key: 00000000-0000-4000-8000-000000000000',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'example.com',
'years' => 1,
'nameservers' => [
'ns1.example.net',
'ns2.example.net',
],
'registrant' => [
'first_name' => 'Mario',
'last_name' => 'Rossi',
'organization' => 'Example Srl',
'email' => 'mario.rossi@example.com',
'phone' => '+39.0212345678',
'street1' => 'Via Roma 1',
'city' => 'Milano',
'state_province' => 'MI',
'postal_code' => '20121',
'country_code' => 'IT',
],
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/register', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key': '00000000-0000-4000-8000-000000000000',
},
body: JSON.stringify({
"name": "example.com",
"years": 1,
"nameservers": [
"ns1.example.net",
"ns2.example.net"
],
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
}),
});
console.log(await response.json());
200
{
"data": {
"state": "completed",
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5310,
"action": "register",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "billable"
}
]
}
}
202
{
"data": {
"state": "pending",
"domain": {
"id": 1042,
"name": "example.com",
"status": "pending_registration",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5310,
"action": "register",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "pending"
}
]
}
}
Error responses share one format: see Errors.
.it extra fields
Some TLDs need registry-specific data, sent in extra_fields as an object of string values; checkboxes are sent as "1", and keys a TLD does not declare are ignored. Ask the API which fields a TLD needs: GET /reseller/tlds/{tld}/extra-fields returns each field with its type, options, pattern and the conditions that make it required (required_if) or shown (visible_if) — the same rules registration validates against. Build your form from it rather than hard-coding the list: it can change.
Get a TLD's extra fields #
GET
/reseller/tlds/{tld}/extra-fields
The registry-specific data a TLD requires on POST /reseller/domains/register
and /transfer, in extra_fields (e.g. the entity type and tax codes of
.it). Read it to build your form instead of learning each field from a
422 invalid_contact. tld is the extension, with or without the dot
(it, .it, edu.it). A TLD with no extra fields answers an empty
fields list.
For each field: send it when visible_if holds (null: always); it is
mandatory when required is true, or when required_if holds. A
condition is {"field", "op", "value"} with op one of eq, ne
(value a string) or in, not_in (value a list of strings), or a
group {"all": [...]} / {"any": [...]} of conditions. field names
another extra field or a registrant field, compared as a string; a
field with no value counts as different for ne and not_in.
A select accepts only the value of one of its options. A text
with a pattern must match it: a Unicode regular expression, already
anchored, with no delimiters or flags (use it with the u flag). A ticked checkbox is sent
as its checked_value, "1"; a mandatory one must be ticked. Labels
and descriptions are English.
The answer is the same as the refusal a register would give: an unknown
TLD, and one not on your price list, answer 422 tld_not_sellable.
This describes what a NEW registration or transfer of the extension
needs, with its default registry connection. For an EXISTING domain the
authoritative set is the extra_fields of
GET /reseller/domains/{name}/contacts: a domain managed through
another registry connection can use different keys.
- Authentication: bearer Token
- Rate limit: 300 req/min per account
Parameters
Path
-
tldstring required
Responses
-
200The TLD’s extra fields.
Response body
application/json-
dataobject2 properties
-
extensionstringThe TLD, canonical: lower case, leading dot, Punycode. -
fieldsarray of objectIn the order the form shows them. Empty when the TLD needs no extra data.10 properties
-
keystringThe key to send inextra_fields. -
labelstring -
descriptionstring | null -
typestringThe input: freetext, aselectamongoptions, or acheckbox.Allowed valuestextselectcheckbox -
requiredbooleantruewhen the field is always mandatory (while visible). -
required_ifmap of any | nullWhenrequiredisfalse: the condition that makes the field mandatory, ornull(optional).{"field", "op", "value"}or a group{"all": […]}/{"any": […]}. -
visible_ifmap of any | nullThe condition under which the field is asked for, same form asrequired_if;null: always. Do not send a field whose condition does not hold. -
optionsarray of objectThe accepted values of aselect, in display order; empty for other types.2 properties
-
valuestring -
labelstring
-
-
patternstring | nullA Unicode regular expression the value must match, anchored (^…$), without delimiters or flags — usable as a JSON Schema / ECMA-262 pattern with theuflag, ornull. -
checked_valuestring | nullFor acheckbox, the value that ticks it:"1".nullfor other types.
-
-
-
-
401unauthenticatedApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundApiError -
422tld_not_sellableApiError -
429rate_limitedApiError
cURL
curl 'https://portal.supporthost.com/api/v1/reseller/tlds/{tld}/extra-fields' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/tlds/{tld}/extra-fields');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/tlds/{tld}/extra-fields', {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
},
});
console.log(await response.json());
200
{
"data": {
"extension": ".it",
"fields": [
{
"key": "entity_type",
"label": "Entity type",
"description": "The legal nature of the registrant.",
"type": "select",
"required": true,
"required_if": null,
"visible_if": null,
"options": [
{
"value": "1",
"label": "Italian and foreign natural persons"
}
],
"pattern": null,
"checked_value": null
}
]
}
}
Error responses share one format: see Errors.
.com, .net and .org need none. For .it, today the endpoint returns these:
Key | Meaning | Required |
|---|---|---|
entity_type | Registrant type: 1 natural person (Italian or foreign) · 2 company · 3 sole proprietorship / freelancer · 4 non-profit organisation · 5 public entity · 6 other entity · 7 foreign entity (equivalent to 2–6, not natural persons) | Always |
codice_fiscale | Italian tax code or ID document number; 5–36 letters, digits, dots, hyphens or spaces | When entity_type is 1 |
partita_iva | VAT number / numeric tax code; exactly 11 digits | When entity_type is 2 to 6 |
tax_code_estero | Foreign tax code; 2–36 letters, digits, dots, hyphens or spaces | When entity_type is 7 |
consent_publishing | Consent to publish the registrant’s data in the WHOIS; send "1" | When entity_type is not 1 or 3; optional otherwise |
accept_terms | Acceptance of the .it registration rules; send "1" | Always |
For example, a company:
"extra_fields": {
"entity_type": "2",
"partita_iva": "01234567890",
"consent_publishing": "1",
"accept_terms": "1"
}
organization is accepted on all four sandbox TLDs but required on none.
When the contact is refused
A missing or invalid registrant or extra field answers 422 with the code invalid_contact. Its fields map names every field to fix, with the messages for each, so you can show them next to your form fields:
{
"error": {
"code": "invalid_contact",
"message": "The registrant contact or the extension fields are not valid.",
"fields": {
"registrant.phone": ["The phone must be in the format +CC.NUMBER."],
"extra_fields.partita_iva": ["The VAT number must be 11 digits."]
}
}
}
Other request problems answer invalid_request with the same fields map. The exact messages are examples: branch on the keys, not on the text.
Wait for the pending state
A money-moving call answers with one of three state values: completed (200, done), pending (202, the registry has taken the request and will finish it later) or, on renew and restore only, not_settled (202, see below). A register call answers either 200 ("state": "completed", the domain is registered) or 202 ("state": "pending"). With 202 your credit is already debited; the charge stays pending until the operation completes, then it is billed on your next monthly invoice.
If an accepted operation fails later, what happens to the money depends on the operation. A register or transfer that ends up failed is refunded automatically. Renew and restore follow different rules: see Refunds on renew and restore. In every case, do not retry with a new Idempotency-Key.
Poll the domain by name until status leaves pending_registration (or pending_transfer). This call reads the state stored on SupportHost and never contacts the registry, so it is cheap; polling every minute or so is plenty.
Get a domain by name #
GET
/reseller/domains/{name}
Returns the current state of one of your domains: status, dates,
auto-renew and whether a transfer is pending or failed. The id in the
response is the one the Domain management calls are addressed by.
Use it to poll an operation that answered 202. It reads the state
stored on this server and never contacts the registry, so it is cheap
to call.
- Authentication: bearer Token
- Rate limit: 300 req/min per account
Parameters
Path
-
namestring required
Responses
-
200The domain.
Response body
application/json-
dataobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
cURL
curl 'https://portal.supporthost.com/api/v1/reseller/domains/{name}' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/{name}');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/{name}', {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
},
});
console.log(await response.json());
200
{
"data": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
}
}
Error responses share one format: see Errors.
A successful registration ends in active. A failed transfer shows "transfer": {"failed": true}. The response id is the numeric id the Domain management calls use.
Renew a domain
Renew an active domain, or one in grace (which also pays the TLD’s grace fee). A domain in redemption answers 409 restore_required: restore it first. If the domain already has an open renewal invoice, your credit pays that invoice instead of creating a charge (paid_invoice_id, paid_invoice_covers).
Renew a domain #
POST
/reseller/domains/{name}/renew
Adds years to an active or grace domain, paid from your credit. A
domain in grace also pays the TLD’s grace fee (a separate grace_fee
charge). A domain in redemption must be restored first
(409 restore_required).
If the domain already has an open renewal invoice for that term, your
credit pays that invoice instead of creating a charge: charges is then
empty and paid_invoice_id / paid_invoice_covers name the invoice and
the domains it renewed (an invoice can cover several of your domains;
paid_invoice_covers_truncated: true means the list was cut at 50).
- Authentication: bearer Token
- Rate limit: 30 req/min per account
Parameters
Path
-
namestring required
Header
-
Idempotency-KeystringOptional. 1–255 printable ASCII characters, scoped to your account. A retry with the same key and the same request returns the stored response byte for byte (withIdempotent-Replayed: true) and never charges twice. The same key with a different request answers422 idempotency_key_reused; while the first request is still running,409 idempotency_in_progress(retry afterretry_afterseconds). Keys are kept for 30 days. See Idempotency in the introduction.-
Min length
1 -
Max length
255
-
Min length
Body application/json
required
-
yearsinteger requiredYears to add. Must be one of therenewtermsGET /reseller/pricinglists for the TLD, otherwise422 invalid_years(withallowed_years).-
Minimum
1 -
Maximum
999
-
Minimum
Responses
-
200Renewed.
Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject6 properties
-
statestring-
Value
completed
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
paid_invoice_idinteger -
paid_invoice_coversarray of string -
paid_invoice_covers_truncatedboolean
-
-
202Accepted — completes laterAccepted but not confirmed yet.
pending: the registrar has not confirmed the operation; your credit has already been used, and its charges staypendinguntil the domain’s dates move, then they are billed (pollGET /reseller/domains/{name}and watchexpiration_date/next_due_date).not_settled: your credit was applied to an open renewal invoice that it did not fully pay, so the domain is NOT renewed yet. In neither case is anything refunded automatically: if the operation did not happen, support follows up. Do not retry with a new Idempotency-Key.Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject6 properties
-
statestringAllowed valuespendingnot_settled -
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
paid_invoice_idinteger -
paid_invoice_coversarray of string -
paid_invoice_covers_truncatedboolean
-
-
401unauthenticatedApiError -
402insufficient_creditApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundnot_foundApiError -
409restore_requiredrenewal_in_progressoperation_in_progressidempotency_in_progressApiError -
422invalid_requestinvalid_yearsdomain_not_renewableregistrar_errorinvalid_idempotency_keyidempotency_key_reusedApiError -
424registrar_unknownApiError -
429rate_limitedApiError
cURL
# Idempotency-Key is optional: a retry with the same key is never processed twice.
curl -X POST 'https://portal.supporthost.com/api/v1/reseller/domains/{name}/renew' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 00000000-0000-4000-8000-000000000000' \
-d '{
"years": 1
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/{name}/renew');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key: 00000000-0000-4000-8000-000000000000',
],
CURLOPT_POSTFIELDS => json_encode([
'years' => 1,
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/{name}/renew', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key': '00000000-0000-4000-8000-000000000000',
},
body: JSON.stringify({
"years": 1
}),
});
console.log(await response.json());
200
{
"data": {
"state": "completed",
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2028-09-15",
"next_due_date": "2028-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5311,
"action": "renew",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "billable"
}
],
"paid_invoice_id": 20790,
"paid_invoice_covers": [
"example.com",
"example.net"
],
"paid_invoice_covers_truncated": true
}
}
202
{
"data": {
"state": "pending",
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5311,
"action": "renew",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "pending"
}
],
"paid_invoice_id": 20790,
"paid_invoice_covers": [
"example.com",
"example.net"
],
"paid_invoice_covers_truncated": true
}
}
Error responses share one format: see Errors.
Refunds on renew and restore
- Refused at once (
422 registrar_error): the amount charged is refunded automatically. - Accepted (
202,pendingornot_settled): never refunded automatically. If the operation does not happen, SupportHost follows up. Poll the domain and watchexpiration_date/next_due_datemove; do not retry with a new Idempotency-Key.
When the invoice is not settled
Rarely, the open renewal invoice is paid from your credit but still does not reach Paid, because its balance grew in the meantime. The call then answers 202 with "state": "not_settled", an empty charges list, and paid_invoice_id / paid_invoice_covers for the payment that was made. The domain is not renewed yet, and nothing is refunded automatically: SupportHost follows up. Do not retry with a new Idempotency-Key; contact support if you need it settled sooner.
Transfer a domain in
The body is the register body plus auth_code, the EPP code from the current registrar; years is not accepted, a transfer buys the TLD’s minimum term. The same extra_fields rules apply. Most transfers answer 202 and take from hours to several days: poll as above. A name that is not registered answers 422 domain_not_registered and nothing is charged.
Transfer a domain in #
POST
/reseller/domains/transfer
Starts the transfer of a registered name to your account, paid from your
credit at your net transfer price. The body is the register body plus
auth_code (the EPP code from the current registrar); years is not
accepted, a transfer always buys the TLD’s minimum term.
registrant, nameservers and extra_fields follow the same rules as
POST /reseller/domains/register, including the per-TLD extra_fields
and the 422 invalid_contact answer with its fields map.
Most transfers answer 202: the registry and the losing registrar take
from hours to several days to complete one. The charge is refunded
automatically if the transfer fails.
- Authentication: bearer Token
- Rate limit: 30 req/min per account
Parameters
Header
-
Idempotency-KeystringOptional. 1–255 printable ASCII characters, scoped to your account. A retry with the same key and the same request returns the stored response byte for byte (withIdempotent-Replayed: true) and never charges twice. The same key with a different request answers422 idempotency_key_reused; while the first request is still running,409 idempotency_in_progress(retry afterretry_afterseconds). Keys are kept for 30 days. See Idempotency in the introduction.-
Min length
1 -
Max length
255
-
Min length
Body application/json
required
-
namestring requiredThe domain to transfer in, e.g.example.org.-
Max length
255
-
Max length
-
nameserversarray of string2 to 5 nameserver host names. Optional: when omitted, the default nameservers configured on this server are used.-
Min items
2 -
Max items
5 -
Pattern
^[A-Za-z0-9.\-]+$ -
Max length
255
-
Min items
-
registrantobject requiredThe owner contact of the domain, the only contact these calls take. The registrar fills the other roles the TLD needs (admin, tech, billing) on its own: most copy the registrant, some use contacts of their own. Change any role afterwards withPUT /reseller/domains/{name}/contacts.11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | nullCompany name. Some TLDs require it and some refuse it: either case answers422 invalid_contact.-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring requiredInternational format+CC.NUMBER, e.g.+39.0212345678. You may instead send a local number here together withregistrant.phone_country_code(ISO country, e.g.IT).-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring requiredISO 3166-1 alpha-2 country code, e.g.IT. When the TLD listsregistrant_countriesinGET /reseller/pricing, it must be one of them, otherwise422 invalid_contactbefore any credit is taken.-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
extra_fieldsmap of stringRegistry-specific data some TLDs require (e.g. entity type, tax codes, language). Which keys are required depends on the TLD and on this server’s configuration, and some are only required depending on the answer to another field. For a new registration or transfer,GET /reseller/tlds/{tld}/extra-fieldslists them, with their conditions; for an existing domain,GET /reseller/domains/{name}/contactsreturns the ones it uses. Values are strings: send a checkbox as"1". Keys the TLD does not declare are ignored. A missing or invalid field answers422 invalid_contactwith afieldsmap naming each field to fix. -
sourceobjectOptional. The additional domain fields of your own platform, sent as your platform holds them, for this server to translate intoextra_fields. A registrar module generated by this server fills it in: you do not need it when you sendextra_fieldsyourself. A key you send inextra_fieldsalways wins over a translated one.5 properties
-
platformstringYour platform, lower case:whmcs,hostbill.-
Pattern
^[a-z][a-z0-9-]*$ -
Max length
32
-
Pattern
-
versionstring | nullYour platform’s own version, e.g.8.13.1: field names differ between versions.-
Max length
50
-
Max length
-
module_versionstring | nullVersion of the registrar module sending the request.-
Max length
50
-
Max length
-
fieldsmap of stringThe platform’s additional fields, name => value, exactly as the platform holds them (e.g."Legal Type": "Companies/one man companies"). At most 50, values up to 255 characters. -
translatedarray of stringTheextra_fieldskeys your module translated itself fromsource.fields. This server’s own translation wins over them; keys not listed here are yours and win over it.-
Max items
100 -
Max length
64
-
Max items
-
-
auth_codestring requiredThe transfer authorisation (EPP) code, obtained from the domain’s current registrar. Always required, whatever the TLD: the panel does not know reliably which registries transfer without one, so it never starts a transfer without it.-
Max length
255
-
Max length
Responses
-
200Transferred.
Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject3 properties
-
statestring-
Value
completed
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
-
202Accepted — completes laterAccepted but not completed yet: the registry has taken the request and will finish it later. Your credit has already been debited; the charges stay
pendinguntil the operation completes (then they are billed on your next monthly invoice) and are refunded automatically if it fails. PollGET /reseller/domains/{name}untilstatusleavespending_registration/pending_transfer(a failed transfer showstransfer.failed: true).Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject3 properties
-
statestring-
Value
pending
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
-
401unauthenticatedApiError -
402insufficient_creditApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundApiError -
409operation_in_progressidempotency_in_progressApiError -
422invalid_requestinvalid_contactinvalid_domaintld_not_sellableinvalid_yearsdomain_unavailabledomain_not_registeredregistration_data_missingregistrar_errorinvalid_idempotency_keyidempotency_key_reusedApiError -
424registrar_unknownApiError -
429rate_limitedApiError -
500operation_failedApiError -
503whois_unavailableApiError
cURL
# Idempotency-Key is optional: a retry with the same key is never processed twice.
curl -X POST 'https://portal.supporthost.com/api/v1/reseller/domains/transfer' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 00000000-0000-4000-8000-000000000000' \
-d '{
"name": "example.org",
"auth_code": "kX7#p2Qz9w",
"nameservers": [
"ns1.example.net",
"ns2.example.net"
],
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/transfer');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key: 00000000-0000-4000-8000-000000000000',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'example.org',
'auth_code' => 'kX7#p2Qz9w',
'nameservers' => [
'ns1.example.net',
'ns2.example.net',
],
'registrant' => [
'first_name' => 'Mario',
'last_name' => 'Rossi',
'organization' => 'Example Srl',
'email' => 'mario.rossi@example.com',
'phone' => '+39.0212345678',
'street1' => 'Via Roma 1',
'city' => 'Milano',
'state_province' => 'MI',
'postal_code' => '20121',
'country_code' => 'IT',
],
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/transfer', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key': '00000000-0000-4000-8000-000000000000',
},
body: JSON.stringify({
"name": "example.org",
"auth_code": "kX7#p2Qz9w",
"nameservers": [
"ns1.example.net",
"ns2.example.net"
],
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
}),
});
console.log(await response.json());
200
{
"data": {
"state": "completed",
"domain": {
"id": 1043,
"name": "example.org",
"status": "active",
"registration_date": "2021-05-03",
"expiration_date": "2027-05-03",
"next_due_date": "2027-05-03",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5312,
"action": "transfer",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "billable"
}
]
}
}
202
{
"data": {
"state": "pending",
"domain": {
"id": 1043,
"name": "example.org",
"status": "pending_transfer",
"registration_date": null,
"expiration_date": null,
"next_due_date": null,
"auto_renew": false,
"transfer": {
"pending": true,
"failed": false
}
},
"charges": [
{
"id": 5312,
"action": "transfer",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "pending"
}
]
}
}
Error responses share one format: see Errors.
Restore from redemption
Recovers a domain in redemption and renews it for one year. It is charged as a one-year renewal plus the TLD’s redemption fee. No body. Like a renewal, a restore that pays an open renewal invoice can end in not_settled; refunds follow the same rules as a renewal.
Restore a domain from redemption #
POST
/reseller/domains/{name}/restore
Recovers a domain in redemption and renews it for one year, paid from
your credit: a restore charge priced as a one-year renewal, plus the
TLD’s redemption fee as a separate redemption_fee charge.
No body. Like renew, it pays an open renewal invoice for the domain
instead when there is one (paid_invoice_* fields, empty charges).
- Authentication: bearer Token
- Rate limit: 30 req/min per account
Parameters
Path
-
namestring required
Header
-
Idempotency-KeystringOptional. 1–255 printable ASCII characters, scoped to your account. A retry with the same key and the same request returns the stored response byte for byte (withIdempotent-Replayed: true) and never charges twice. The same key with a different request answers422 idempotency_key_reused; while the first request is still running,409 idempotency_in_progress(retry afterretry_afterseconds). Keys are kept for 30 days. See Idempotency in the introduction.-
Min length
1 -
Max length
255
-
Min length
Responses
-
200Restored.
Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject6 properties
-
statestring-
Value
completed
-
Value
-
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
paid_invoice_idinteger -
paid_invoice_coversarray of string -
paid_invoice_covers_truncatedboolean
-
-
202Accepted — completes laterAccepted but not confirmed yet.
pending: the registrar has not confirmed the operation; your credit has already been used, and its charges staypendinguntil the domain’s dates move, then they are billed (pollGET /reseller/domains/{name}and watchexpiration_date/next_due_date).not_settled: your credit was applied to an open renewal invoice that it did not fully pay, so the domain is NOT renewed yet. In neither case is anything refunded automatically: if the operation did not happen, support follows up. Do not retry with a new Idempotency-Key.Idempotent-Replayedstring response headertruewhen this response is the replay of an earlier request with the same Idempotency-Key.
Response body
application/json-
dataobject6 properties
-
statestringAllowed valuespendingnot_settled -
domainobject8 properties
-
idinteger -
namestring -
statusstringAllowed valuespendingpending_registrationpending_transferactiveexpiredgraceredemptionpending_deletetransferring_awaytransferred_awaytransfer_failedcancelledfraud -
registration_datestring | null -
expiration_datestring | null -
next_due_datestring | null -
auto_renewboolean -
transferobject2 properties
-
pendingboolean -
failedboolean
-
-
-
chargesarray of object8 properties
-
idinteger -
actionstringAllowed valuesregistertransferrenewrestoregrace_feeredemption_fee -
yearsinteger -
currencystring -
netnumber -
taxnumber -
grossnumber -
statusstringAllowed valuespendingbillableinvoicedrefunded
-
-
paid_invoice_idinteger -
paid_invoice_coversarray of string -
paid_invoice_covers_truncatedboolean
-
-
401unauthenticatedApiError -
402insufficient_creditApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disabledApiError -
404client_not_foundnot_foundApiError -
409renewal_in_progressoperation_in_progressidempotency_in_progressApiError -
422domain_not_restorableregistrar_errorinvalid_idempotency_keyidempotency_key_reusedApiError -
424registrar_unknownApiError -
429rate_limitedApiError
cURL
# Idempotency-Key is optional: a retry with the same key is never processed twice.
curl -X POST 'https://portal.supporthost.com/api/v1/reseller/domains/{name}/restore' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Idempotency-Key: 00000000-0000-4000-8000-000000000000'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/{name}/restore');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key: 00000000-0000-4000-8000-000000000000',
],
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/{name}/restore', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
// Optional: a retry with the same key is never processed twice.
'Idempotency-Key': '00000000-0000-4000-8000-000000000000',
},
});
console.log(await response.json());
200
{
"data": {
"state": "completed",
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2028-09-15",
"next_due_date": "2028-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5313,
"action": "restore",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "billable"
}
],
"paid_invoice_id": 20790,
"paid_invoice_covers": [
"example.com",
"example.net"
],
"paid_invoice_covers_truncated": true
}
}
202
{
"data": {
"state": "pending",
"domain": {
"id": 1042,
"name": "example.com",
"status": "redemption",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
},
"charges": [
{
"id": 5313,
"action": "restore",
"years": 1,
"currency": "EUR",
"net": 11.1699999999999999289457264239899814128875732421875,
"tax": 2.45999999999999996447286321199499070644378662109375,
"gross": 13.6300000000000007815970093361102044582366943359375,
"status": "pending"
}
],
"paid_invoice_id": 20790,
"paid_invoice_covers": [
"example.com",
"example.net"
],
"paid_invoice_covers_truncated": true
}
}
Error responses share one format: see Errors.
Manage the domain
The EPP code, the transfer lock, WHOIS privacy and auto-renew are in Domain management. Those calls are addressed by the numeric id from GET /reseller/domains/{name}, not by the name. Contacts have their own reseller calls, below. For example, to change the nameservers:
Change the nameservers #
PUT
/client/domains/{domain}/nameservers
Replaces the domain’s nameservers at the registry with the list you
send (2 to 13 host names). completed: false means the registry
accepted the change but applies it later; message explains.
Free of charge. Refused with 403 domain_not_active when the domain’s status does not
allow it (e.g. expired).
- Authentication: bearer Token
- Rate limit: 120 req/min per user
Parameters
Path
-
domaininteger requiredThe domain ID
Body application/json
required
-
nameserversarray of string required2 to 13 nameserver host names, all different. The list replaces the current one.-
Min items
2 -
Max items
13 - Unique items
-
Pattern
^(?=.{1,253}$)([a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)+$ -
Max length
253
-
Min items
Responses
-
200Response body
application/json-
dataobject2 properties
-
completedboolean -
messagestring
-
-
-
401unauthenticatedApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddendomain_not_activeApiError -
404client_not_foundnot_foundApiError -
422invalid_requestunprocessableApiError -
429rate_limitedApiError
cURL
curl -X PUT 'https://portal.supporthost.com/api/v1/client/domains/{domain}/nameservers' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"nameservers": [
"ns1.example.net",
"ns2.example.net",
"ns3.example.net"
]
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/client/domains/{domain}/nameservers');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'nameservers' => [
'ns1.example.net',
'ns2.example.net',
'ns3.example.net',
],
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/client/domains/{domain}/nameservers', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"nameservers": [
"ns1.example.net",
"ns2.example.net",
"ns3.example.net"
]
}),
});
console.log(await response.json());
200
{
"data": {
"completed": true,
"message": "Nameservers updated."
}
}
Error responses share one format: see Errors.
Nameservers can also be set at registration with nameservers (2 to 5 host names); when omitted, SupportHost’s default nameservers are used.
Contacts
Read every contact the domain uses in one call: GET /reseller/domains/{name}/contacts returns the roles the registry uses (required_types), one contact per role, the registry-specific extra_fields of the registrant, and any role you cannot change (read_only). For an existing domain these extra_fields are the authoritative list — GET /reseller/tlds/{tld}/extra-fields describes what a new registration needs.
To change contacts, send the same shape back with PUT /reseller/domains/{name}/contacts: the full contact for each role you change. Roles you leave out stay as they are, and roles identical to the current data are skipped. Everything is validated before anything is written; then the roles are applied in order — admin, tech, billing, registrant last — and the call stops at the first one the registry refuses, telling you in results what was updated. It is free; 202 means a change is still waiting for the registry.
Some registries change the registrant only through a change-of-holder procedure (a trade): the call then answers 409 registrant_change_requires_trade after applying the other roles — contact SupportHost to change the owner. A domain that isn’t active answers 403 domain_not_active, with its domain_status.
Update the contacts of a domain #
PUT
/reseller/domains/{name}/contacts
Changes the roles you send, each as a full contact (the registrant
shape of POST /reseller/domains/register); roles you leave out stay
as they are. extra_fields (and source) belong to the registrant and
are accepted only with contacts.registrant; registry fields you do not
send keep the value on file. Free of charge.
Every role is validated before anything is written: a failure is
invalid_contact with fields keyed per role (contacts.admin.email,
extra_fields.codice_fiscale). A role you send whose content equals the
registry’s is not re-sent (unchanged).
Roles are applied one at a time — admin, tech, billing, then the
registrant — and the run stops at the first failure. results reports
each role sent: updated, pending (accepted, applied later: a
registrant confirming by email, say), unchanged, failed or
not_attempted. On a failure the error carries the same results, so
you know which roles were already applied.
A registrant change the registry accepts only through a
change-of-holder procedure is refused with
registrant_change_requires_trade (or
registrant_change_requires_postal_procedure): nothing is sent for the
registrant and nothing is charged.
200 with state: "completed" when every role sent is done; 202 with
state: "pending" when at least one is waiting on the registry.
- Authentication: bearer Token
- Rate limit: 30 req/min per account
Parameters
Path
-
namestring required
Body application/json
required
-
contactsobject requiredThe roles to change, keyedregistrant,admin,tech,billing. Send only roles the domain uses (required_typesofGET /reseller/domains/{name}/contacts); any other key answers422 invalid_contactnamingcontacts.<role>. Roles you leave out stay as they are.4 properties
-
registrantobjectThe owner contact, as a full contact (theregistrantofPOST /reseller/domains/register).11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | nullCompany name. Some TLDs require it and some refuse it: either case answers422 invalid_contact.-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring requiredInternational format+CC.NUMBER, e.g.+39.0212345678. You may instead send a local number here together withregistrant.phone_country_code(ISO country, e.g.IT).-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring requiredISO 3166-1 alpha-2 country code, e.g.IT.-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
adminobjectThe administrative contact, as a full contact (same fields as the registrant). Organization is always optional here.11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | null-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring required-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring required-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
techobjectThe technical contact, as a full contact.11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | null-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring required-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring required-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
billingobjectThe billing contact, as a full contact.11 properties
-
first_namestring required-
Max length
100
-
Max length
-
last_namestring required-
Max length
100
-
Max length
-
organizationstring | null-
Max length
200
-
Max length
-
emailstring required-
Format
email -
Max length
200
-
Format
-
phonestring required-
Pattern
^\+[0-9]{1,3}\.[0-9]{1,14}$ -
Max length
30
-
Pattern
-
street1string required-
Max length
200
-
Max length
-
street2string | null-
Max length
200
-
Max length
-
citystring required-
Max length
100
-
Max length
-
state_provincestring | null-
Max length
100
-
Max length
-
postal_codestring required-
Max length
20
-
Max length
-
country_codestring required-
Min length
2 -
Max length
2
Allowed valuesAFALDZADAOAIAQAGSAAR240 more values
AMAWAUATAZBSBHBDBBBEBZBJBMBTBYBOBABWBRBNBGBFBIKHCMCACVBQCZTDCLCNCYVACOKMCDCGKPKRCRCIHRCUCWDKDMECEGSVAEEREESZETFJPHFIFRGAGMGEGSDEGHJMJPGIDJJOGRGDGLGPGUGTGGGNGQGWGYGFHTHNINIDIRIQIEISBVCXNFIMKYCCCKFKFOHMMPMHUMPNSBTCVIVGAXILITJEKZKEKGKIXKKWLALSLVLBLRLYLILTLUMKMGMWMYMVMLMTMAMQMRMUYTMXFMMDMCMNMEMSMZMMNANRNPNINENGNUNONCNZOMNLPKPWPAPGPYPEPFPLPTPRQAHKMOGBCFDORERORWRUEHKNLCMFVCBLPMWSASSMSHSNRSSCSLSGSXSYSKSISOESLKUSSSZASDSRSJSECHSTTJTWTZTFPSIOTHTLTGTKTOTTTNTRTMTVUAUGHUUYUZVUVEVNWFYEZMZW -
Min length
-
-
-
extra_fieldsmap of stringThe registrant’s registry-specific fields: the keysGET /reseller/domains/{name}/contactsreturns inextra_fields, which is the authoritative set for an existing domain (it can differ fromGET /reseller/tlds/{tld}/extra-fields, which describes a new registration). Only together withcontacts.registrant. Fields you do not send keep the value on file. Values are strings: send a checkbox as"1". A missing or invalid field answers422 invalid_contactnamingextra_fields.<key>. -
sourceobjectOptional, only together withcontacts.registrant. The additional domain fields of your own platform, for this server to translate intoextra_fields(seePOST /reseller/domains/register).5 properties
-
platformstringYour platform, lower case:whmcs,hostbill.-
Pattern
^[a-z][a-z0-9-]*$ -
Max length
32
-
Pattern
-
versionstring | nullYour platform’s own version, e.g.8.13.1: field names differ between versions.-
Max length
50
-
Max length
-
module_versionstring | nullVersion of the registrar module sending the request.-
Max length
50
-
Max length
-
fieldsmap of stringThe platform’s additional fields, name => value, exactly as the platform holds them (e.g."Legal Type": "Companies/one man companies"). At most 50, values up to 255 characters. -
translatedarray of stringTheextra_fieldskeys your module translated itself fromsource.fields. This server’s own translation wins over them; keys not listed here are yours and win over it.-
Max items
100 -
Max length
64
-
Max items
-
Responses
-
200Every role sent is done.
Response body
application/json-
dataobject3 properties
-
statestringcompleted(200): every role sent is done.pending(202): at least one role was accepted by the registry but applies later — typically a registrant confirming the change by email.Allowed valuescompletedpending -
resultsmap of stringOne entry per role sent, in the order they were applied:updated,pending, orunchanged(the registry already held exactly this data, so nothing was sent). The same map, withfailedandnot_attempted, rides on acontact_update_failedorregistrant_change_requires_*error so you know which roles were already applied. -
domainmap of any
-
-
-
202Accepted — completes laterAccepted; at least one role is waiting on the registry.
Response body
application/json-
dataobject3 properties
-
statestringcompleted(200): every role sent is done.pending(202): at least one role was accepted by the registry but applies later — typically a registrant confirming the change by email.Allowed valuescompletedpending -
resultsmap of stringOne entry per role sent, in the order they were applied:updated,pending, orunchanged(the registry already held exactly this data, so nothing was sent). The same map, withfailedandnot_attempted, rides on acontact_update_failedorregistrant_change_requires_*error so you know which roles were already applied. -
domainmap of any
-
-
-
401unauthenticatedApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddennot_resellercredit_disableddomain_not_activeApiError -
404client_not_foundnot_foundApiError -
409registrant_change_requires_traderegistrant_change_requires_postal_procedureApiError -
422invalid_requestinvalid_contactcontact_update_failedcontact_update_not_supportedApiError -
429rate_limitedApiError -
503contacts_unavailableApiError
cURL
curl -X PUT 'https://portal.supporthost.com/api/v1/reseller/domains/{name}/contacts' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"contacts": {
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
},
"admin": {
"first_name": "Laura",
"last_name": "Bianchi",
"organization": "Example Srl",
"email": "hostmaster@example.com",
"phone": "+39.0212345679",
"street1": "Via Roma 1",
"street2": "",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
},
"tech": {
"first_name": "Laura",
"last_name": "Bianchi",
"organization": "Example Srl",
"email": "hostmaster@example.com",
"phone": "+39.0212345679",
"street1": "Via Roma 1",
"street2": "",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
},
"extra_fields": {
"entity_type": "2",
"partita_iva": "01234567890",
"consent_publishing": "1",
"accept_terms": "1"
}
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/reseller/domains/{name}/contacts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'contacts' => [
'registrant' => [
'first_name' => 'Mario',
'last_name' => 'Rossi',
'organization' => 'Example Srl',
'email' => 'mario.rossi@example.com',
'phone' => '+39.0212345678',
'street1' => 'Via Roma 1',
'city' => 'Milano',
'state_province' => 'MI',
'postal_code' => '20121',
'country_code' => 'IT',
],
'admin' => [
'first_name' => 'Laura',
'last_name' => 'Bianchi',
'organization' => 'Example Srl',
'email' => 'hostmaster@example.com',
'phone' => '+39.0212345679',
'street1' => 'Via Roma 1',
'street2' => '',
'city' => 'Milano',
'state_province' => 'MI',
'postal_code' => '20121',
'country_code' => 'IT',
],
'tech' => [
'first_name' => 'Laura',
'last_name' => 'Bianchi',
'organization' => 'Example Srl',
'email' => 'hostmaster@example.com',
'phone' => '+39.0212345679',
'street1' => 'Via Roma 1',
'street2' => '',
'city' => 'Milano',
'state_province' => 'MI',
'postal_code' => '20121',
'country_code' => 'IT',
],
],
'extra_fields' => [
'entity_type' => '2',
'partita_iva' => '01234567890',
'consent_publishing' => '1',
'accept_terms' => '1',
],
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/reseller/domains/{name}/contacts', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contacts": {
"registrant": {
"first_name": "Mario",
"last_name": "Rossi",
"organization": "Example Srl",
"email": "mario.rossi@example.com",
"phone": "+39.0212345678",
"street1": "Via Roma 1",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
},
"admin": {
"first_name": "Laura",
"last_name": "Bianchi",
"organization": "Example Srl",
"email": "hostmaster@example.com",
"phone": "+39.0212345679",
"street1": "Via Roma 1",
"street2": "",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
},
"tech": {
"first_name": "Laura",
"last_name": "Bianchi",
"organization": "Example Srl",
"email": "hostmaster@example.com",
"phone": "+39.0212345679",
"street1": "Via Roma 1",
"street2": "",
"city": "Milano",
"state_province": "MI",
"postal_code": "20121",
"country_code": "IT"
}
},
"extra_fields": {
"entity_type": "2",
"partita_iva": "01234567890",
"consent_publishing": "1",
"accept_terms": "1"
}
}),
});
console.log(await response.json());
200
{
"data": {
"state": "completed",
"results": {
"admin": "updated",
"tech": "unchanged",
"registrant": "updated"
},
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
}
}
}
202
{
"data": {
"state": "pending",
"results": {
"admin": "updated",
"registrant": "pending"
},
"domain": {
"id": 1042,
"name": "example.com",
"status": "active",
"registration_date": "2026-09-15",
"expiration_date": "2027-09-15",
"next_due_date": "2027-09-15",
"auto_renew": false,
"transfer": {
"pending": false,
"failed": false
}
}
}
}
Error responses share one format: see Errors.
Releasing a .uk domain
.uk domains (.co.uk, .org.uk and the other .uk extensions) do not leave with an EPP code: they are released to the receiving registrar by its tag. Call POST /client/domains/{id}/release with that tag; it cannot be undone from our side. On other extensions the call answers 422 domain_release_not_supported — use the EPP code instead.
Release the domain to another registrar #
POST
/client/domains/{domain}/release
Hands the domain over to another registrar, identified by its tag. Some
registries move a domain this way instead of with a transfer code: .uk
domains (including .co.uk, .org.uk and the other .uk extensions) leave
by a tag change, often called the IPS tag. Ask the receiving registrar
for its tag and send it as tag; letters, digits and hyphens, case is
ignored.
Only for a domain whose status is active: any other status answers
403 domain_not_active. A domain whose registry does not release by tag answers
422 domain_release_not_supported: use the EPP code
(POST /client/domains/{domain}/epp) to transfer it instead.
Irreversible from our side: once the receiving registrar has the domain, it is no longer managed from this account and cannot be taken back from here. Check the tag before sending it.
- Authentication: bearer Token
- Rate limit: 10 req/min per user
Parameters
Path
-
domaininteger requiredThe domain ID
Body application/json
required
-
tagstring requiredThe tag of the registrar receiving the domain, as that registrar gives it to you: letters, digits and hyphens, up to 32 characters. Case is ignored. The release is irreversible from our side, so check it before sending.-
Pattern
^[A-Za-z0-9\-]+$ -
Max length
32
-
Pattern
Responses
-
200Response body
application/json-
dataobject2 properties
-
completedboolean -
messagestring
-
-
-
401unauthenticatedApiError -
403client_token_requiredtoken_not_api_enabledtoken_not_client_scopedclient_access_revokedclient_cancelledip_not_allowedip_whitelist_misconfiguredforbiddendomain_not_activeApiError -
404client_not_foundnot_foundApiError -
422domain_release_not_supportedregistrar_errorinvalid_requestunprocessableApiError -
429rate_limitedApiError
cURL
curl -X POST 'https://portal.supporthost.com/api/v1/client/domains/{domain}/release' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"tag": "EXAMPLE-REGISTRAR"
}'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/client/domains/{domain}/release');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'tag' => 'EXAMPLE-REGISTRAR',
]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/client/domains/{domain}/release', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"tag": "EXAMPLE-REGISTRAR"
}),
});
console.log(await response.json());
200
{
"data": {
"completed": true,
"message": "Your domain has been released to the receiving registrar."
}
}
Error responses share one format: see Errors.
DNSSEC
DNSSEC works the same on every extension, and the domain’s DNSSEC state tells you what to send, so no trial and error is needed. Before you register, the price list already says per TLD whether DNSSEC is supported and in which record form (dnssec in GET /reseller/pricing).
- Read the state with
GET /client/domains/{id}/dnssec:supported, therecord_typethe domain takes (dsordnskey), theoperationsavailable now and the acceptedalgorithms/digest_types. - Turn it on: if
enableis inoperations, the zone is signed for you — callPOST …/dnssec/enable. Otherwise add the records your DNS provider gives you withPOST …/dnssec/records, in the formrecord_typenames. - Check the result: every call answers with the refreshed state; while
pendingistruethe registry has accepted the change but not applied it yet — read the state again later.
DNSSEC changes only while the domain is active. On any other status operations is empty, blocked_reason says why, and every change answers 403 domain_not_active. Reading the state still works.
Get the DNSSEC state of a domain #
GET
/client/domains/{domain}/dnssec
Start here: the state says what the domain accepts, so the other calls
need no guessing. supported: false (never an error) means DNSSEC
cannot be managed for this domain. Otherwise record_type names the
record form it takes, operations the calls available right now,
algorithms and digest_types the values it accepts, and active
whether DNSSEC is on. Read live on every call.
DNSSEC can be changed only while the domain is active. For any other
status operations is empty and blocked_reason says why; the records
are still read while the registry holds the domain (grace, redemption,
transferring away), and are null when it does not (not registered
yet, or no longer ours).
- Authentication: bearer Token
- Rate limit: 30 req/min per user
Parameters
Path
-
domaininteger requiredThe domain ID
Responses
-
200The domain’s DNSSEC state.
Response body
application/json-
dataobject10 properties
-
supportedbooleantruewhen DNSSEC can be managed for this domain through this API.falseis not an error: DNSSEC is not offered for this domain here, the other fields are then empty and every other DNSSEC call answersdnssec_not_supported. -
record_typestring | nullThe record form this domain takes;nullwhen notsupported.ds: the hash of your key, as your DNS provider shows it (key_tag,algorithm,digest_type,digest).dnskey: the key itself (flags,protocol,algorithm,public_key). Send records in this form only: the other one is refused withinvalid_request.Allowed valuesdsdnskey -
operationsarray of stringThe calls available on this domain right now.add:POST …/dnssec/records.remove:DELETE …/dnssec/records.disable:DELETE …/dnssec(removes every record: DNSSEC off).enable:POST …/dnssec/enable, listed only where the zone is signed for you (typically a domain using our DNS service), which then takes no record. Empty when notsupported, when the domain is notactive(seeblocked_reason), and it can be empty while a change is still being applied (pending: true).Allowed valuesaddremovedisableenable -
algorithmsarray of integerThe algorithm numbers (IANA) this domain accepts inalgorithm, as plain integers:5RSA/SHA-1,7RSASHA1-NSEC3-SHA1,8RSA/SHA-256,10RSA/SHA-512,13ECDSA P-256/SHA-256,14ECDSA P-384/SHA-384,15Ed25519,16Ed448. Often a subset of these. Empty when notsupported.Allowed values5781013141516 -
digest_typesarray of integerdsdomains only (absent fordnskey): the digest type numbers accepted indigest_type, as plain integers:1SHA-1,2SHA-256,4SHA-384.Allowed values124 -
recordsarray of object | nullThe DNSSEC records on file for the domain, each with the fields of the domain’srecord_typeonly. Empty when there is none, and on a zone signed for you, which lists no record (seeactive).nullwhen a change was accepted but the records could not be read back right after it (read the state again), and when the domain is not held at the registry: a domain that is not registered yet, or no longer ours (blocked_reasonis then set). A domain in grace, redemption or transferring away is still read.7 properties
-
key_tagintegerdsonly: the key tag, 0-65535. -
flagsintegerdnskeyonly:257(KSK, the usual one) or256(ZSK). -
protocolintegerdnskeyonly: always3. -
algorithmintegerBoth forms: the DNSSEC algorithm number (IANA), one of the domain’salgorithms. -
digest_typeintegerdsonly: the digest type, one of the domain’sdigest_types. -
digeststringdsonly: the digest, hexadecimal, upper case. -
public_keystringdnskeyonly: the public key, Base64, without whitespace.
-
-
activeboolean | nullWhether DNSSEC is on for the domain. Usually the same asrecordsnot being empty; it also covers a zone signed for you (which lists no record) and DS records set outside this API.nullwhen notsupported, or whenrecordsisnull. -
pendingbooleantruewhen a change was accepted but is not applied yet:recordsandactivestill show the situation before it. PollGET …/dnssecuntil it isfalse; no need to send the change again. -
pending_messagestring | nullA short customer-readable note about the pending change, when there is one; otherwisenull. -
blocked_reasonobject | nullWhy no change can be made right now, ornullwhen changes are possible. Set when the domain’s status is notactive: DNSSEC is changed only on an active domain, like the nameservers and the contacts.operationsis then empty and every change answers403 domain_not_active.2 properties
-
codestringThe code a change would be refused with.Allowed valuesdomain_not_active -
messagestringThe reason, readable by a customer.
-
-
-
cURL
curl 'https://portal.supporthost.com/api/v1/client/domains/{domain}/dnssec' \
-H "Authorization: Bearer $API_TOKEN" \
-H 'Accept: application/json'
PHP
<?php
$ch = curl_init('https://portal.supporthost.com/api/v1/client/domains/{domain}/dnssec');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('API_TOKEN'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
// Error envelope: {"error": {"code": "...", "message": "...", "fields": {...}}}
echo $status . ' ' . ($body['error']['code'] ?? '') . ': ' . ($body['error']['message'] ?? '') . "\n";
} else {
print_r($body);
}
Node.js
const response = await fetch('https://portal.supporthost.com/api/v1/client/domains/{domain}/dnssec', {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
},
});
console.log(await response.json());
200
{
"data": {
"supported": true,
"record_type": "ds",
"operations": [
"add",
"remove",
"disable"
],
"algorithms": [
8,
13,
15
],
"digest_types": [
2,
4
],
"records": [
{
"key_tag": 12345,
"flags": 257,
"protocol": 3,
"algorithm": 13,
"digest_type": 2,
"digest": "E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CFC41A5766",
"public_key": "mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ=="
}
],
"active": true,
"pending": false,
"pending_message": null,
"blocked_reason": null
}
}
Error responses share one format: see Errors.
Every DNSSEC call — enable, disable, add and remove records — is in the DNSSEC reference.