SupportHost italian

Working with domains

On this page

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.

Parameters

Query
  • name string required
    • Max length 255

Responses

  • 200

    Availability and price.

    Response body application/json
    • data object
      6 properties
      • name string
      • available boolean | null
      • currency string
      • register_price number | null
      • transfer_price number | null
      • min_years integer
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found ApiError
  • 422 invalid_request invalid_domain tld_not_sellable ApiError
  • 429 rate_limited ApiError

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());

Response

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.

View in reference

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.

  • years defaults to the TLD’s minimum term; GET /reseller/pricing lists the terms you can buy.
  • nameservers (2–5 host names) is optional: omitted, the default nameservers configured on this server are used.
  • registrant is the owner contact. The phone is +CC.NNNN (e.g. +39.0212345678), or a local number plus phone_country_code (ISO country, e.g. IT).
  • extra_fields is 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 answers 422 invalid_contact, whose fields map names every field to fix.
  • source is optional: your platform’s own additional fields, as your platform holds them, for this server to translate into extra_fields. A key you send in extra_fields wins 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.

Parameters

Header
  • Idempotency-Key string
    Optional. 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 (with Idempotent-Replayed: true) and never charges twice. The same key with a different request answers 422 idempotency_key_reused; while the first request is still running, 409 idempotency_in_progress (retry after retry_after seconds). Keys are kept for 30 days. See Idempotency in the introduction.
    • Min length 1
    • Max length 255

    See Rate limits and idempotency.

Body application/json required
  • name string required
    The 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; otherwise 422 invalid_domain.
    • Max length 255
  • years integer
    Registration term. Defaults to the TLD’s minimum; must be one of the register terms GET /reseller/pricing lists for the TLD, otherwise 422 invalid_years (with allowed_years).
    • Minimum 1
  • nameservers array of string
    2 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
  • registrant object required
    The 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 with PUT /reseller/domains/{name}/contacts.
    11 properties
    • first_name string required
      • Max length 100
    • last_name string required
      • Max length 100
    • organization string | null
      Company name. Some TLDs require it and some refuse it: either case answers 422 invalid_contact.
      • Max length 200
    • email string required
      • Format email
      • Max length 200
    • phone string required
      International format +CC.NUMBER, e.g. +39.0212345678. You may instead send a local number here together with registrant.phone_country_code (ISO country, e.g. IT).
      • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
      • Max length 30
    • street1 string required
      • Max length 200
    • street2 string | null
      • Max length 200
    • city string required
      • Max length 100
    • state_province string | null
      • Max length 100
    • postal_code string required
      • Max length 20
    • country_code string required
      ISO 3166-1 alpha-2 country code, e.g. IT. When the TLD lists registrant_countries in GET /reseller/pricing, it must be one of them, otherwise 422 invalid_contact before any credit is taken.
      • Min length 2
      • Max length 2
      Allowed values AF AL DZ AD AO AI AQ AG SA AR
      240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
  • extra_fields map of string
    Registry-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-fields lists them, with their conditions; for an existing domain, GET /reseller/domains/{name}/contacts returns 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 answers 422 invalid_contact with a fields map naming each field to fix.
  • source object
    Optional. The additional domain fields of your own platform, sent as your platform holds them, for this server to translate into extra_fields. A registrar module generated by this server fills it in: you do not need it when you send extra_fields yourself. A key you send in extra_fields always wins over a translated one.
    5 properties
    • platform string
      Your platform, lower case: whmcs, hostbill.
      • Pattern ^[a-z][a-z0-9-]*$
      • Max length 32
    • version string | null
      Your platform’s own version, e.g. 8.13.1: field names differ between versions.
      • Max length 50
    • module_version string | null
      Version of the registrar module sending the request.
      • Max length 50
    • fields map of string
      The 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.
    • translated array of string
      The extra_fields keys your module translated itself from source.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

Responses

  • 200

    Registered.

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      3 properties
      • state string
        • Value completed
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
  • 202 Accepted — completes later

    Accepted but not completed yet: the registry has taken the request and will finish it later. Your credit has already been debited; the charges stay pending until the operation completes (then they are billed on your next monthly invoice) and are refunded automatically if it fails. Poll GET /reseller/domains/{name} until status leaves pending_registration / pending_transfer (a failed transfer shows transfer.failed: true).

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      3 properties
      • state string
        • Value pending
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
  • 401 unauthenticated ApiError
  • 402 insufficient_credit ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found ApiError
  • 409 operation_in_progress idempotency_in_progress ApiError
  • 422 invalid_request invalid_contact invalid_domain tld_not_sellable invalid_years domain_unavailable registration_data_missing registrar_error invalid_idempotency_key idempotency_key_reused ApiError
  • 424 registrar_unknown ApiError
  • 429 rate_limited ApiError
  • 500 operation_failed ApiError
  • 503 whois_unavailable ApiError

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());

Response

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.

View in reference

.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.

Parameters
Path
  • tld string required
Responses
  • 200

    The TLD’s extra fields.

    Response body application/json
    • data object
      2 properties
      • extension string
        The TLD, canonical: lower case, leading dot, Punycode.
      • fields array of object
        In the order the form shows them. Empty when the TLD needs no extra data.
        10 properties
        • key string
          The key to send in extra_fields.
        • label string
        • description string | null
        • type string
          The input: free text, a select among options, or a checkbox.
          Allowed values text select checkbox
        • required boolean
          true when the field is always mandatory (while visible).
        • required_if map of any | null
          When required is false: the condition that makes the field mandatory, or null (optional). {"field", "op", "value"} or a group {"all": […]} / {"any": […]}.
        • visible_if map of any | null
          The condition under which the field is asked for, same form as required_if; null: always. Do not send a field whose condition does not hold.
        • options array of object
          The accepted values of a select, in display order; empty for other types.
          2 properties
          • value string
          • label string
        • pattern string | null
          A Unicode regular expression the value must match, anchored (^…$), without delimiters or flags — usable as a JSON Schema / ECMA-262 pattern with the u flag, or null.
        • checked_value string | null
          For a checkbox, the value that ticks it: "1". null for other types.
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found ApiError
  • 422 tld_not_sellable ApiError
  • 429 rate_limited ApiError

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());

Response

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.

View in reference

.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.

Parameters

Path
  • name string required

Responses

  • 200

    The domain.

    Response body application/json
    • data object
      8 properties
      • id integer
      • name string
      • status string
        Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
      • registration_date string | null
      • expiration_date string | null
      • next_due_date string | null
      • auto_renew boolean
      • transfer object
        2 properties
        • pending boolean
        • failed boolean
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found not_found ApiError
  • 429 rate_limited ApiError

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());

Response

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.

View in reference

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).

Parameters

Path
  • name string required
Header
  • Idempotency-Key string
    Optional. 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 (with Idempotent-Replayed: true) and never charges twice. The same key with a different request answers 422 idempotency_key_reused; while the first request is still running, 409 idempotency_in_progress (retry after retry_after seconds). Keys are kept for 30 days. See Idempotency in the introduction.
    • Min length 1
    • Max length 255

    See Rate limits and idempotency.

Body application/json required
  • years integer required
    Years to add. Must be one of the renew terms GET /reseller/pricing lists for the TLD, otherwise 422 invalid_years (with allowed_years).
    • Minimum 1
    • Maximum 999

Responses

  • 200

    Renewed.

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      6 properties
      • state string
        • Value completed
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
      • paid_invoice_id integer
      • paid_invoice_covers array of string
      • paid_invoice_covers_truncated boolean
  • 202 Accepted — completes later

    Accepted but not confirmed yet. pending: the registrar has not confirmed the operation; your credit has already been used, and its charges stay pending until the domain’s dates move, then they are billed (poll GET /reseller/domains/{name} and watch expiration_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-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      6 properties
      • state string
        Allowed values pending not_settled
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
      • paid_invoice_id integer
      • paid_invoice_covers array of string
      • paid_invoice_covers_truncated boolean
  • 401 unauthenticated ApiError
  • 402 insufficient_credit ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found not_found ApiError
  • 409 restore_required renewal_in_progress operation_in_progress idempotency_in_progress ApiError
  • 422 invalid_request invalid_years domain_not_renewable registrar_error invalid_idempotency_key idempotency_key_reused ApiError
  • 424 registrar_unknown ApiError
  • 429 rate_limited ApiError

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());

Response

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.

View in reference

Refunds on renew and restore

  • Refused at once (422 registrar_error): the amount charged is refunded automatically.
  • Accepted (202, pending or not_settled): never refunded automatically. If the operation does not happen, SupportHost follows up. Poll the domain and watch expiration_date / next_due_date move; 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.

Parameters

Header
  • Idempotency-Key string
    Optional. 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 (with Idempotent-Replayed: true) and never charges twice. The same key with a different request answers 422 idempotency_key_reused; while the first request is still running, 409 idempotency_in_progress (retry after retry_after seconds). Keys are kept for 30 days. See Idempotency in the introduction.
    • Min length 1
    • Max length 255

    See Rate limits and idempotency.

Body application/json required
  • name string required
    The domain to transfer in, e.g. example.org.
    • Max length 255
  • nameservers array of string
    2 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
  • registrant object required
    The 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 with PUT /reseller/domains/{name}/contacts.
    11 properties
    • first_name string required
      • Max length 100
    • last_name string required
      • Max length 100
    • organization string | null
      Company name. Some TLDs require it and some refuse it: either case answers 422 invalid_contact.
      • Max length 200
    • email string required
      • Format email
      • Max length 200
    • phone string required
      International format +CC.NUMBER, e.g. +39.0212345678. You may instead send a local number here together with registrant.phone_country_code (ISO country, e.g. IT).
      • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
      • Max length 30
    • street1 string required
      • Max length 200
    • street2 string | null
      • Max length 200
    • city string required
      • Max length 100
    • state_province string | null
      • Max length 100
    • postal_code string required
      • Max length 20
    • country_code string required
      ISO 3166-1 alpha-2 country code, e.g. IT. When the TLD lists registrant_countries in GET /reseller/pricing, it must be one of them, otherwise 422 invalid_contact before any credit is taken.
      • Min length 2
      • Max length 2
      Allowed values AF AL DZ AD AO AI AQ AG SA AR
      240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
  • extra_fields map of string
    Registry-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-fields lists them, with their conditions; for an existing domain, GET /reseller/domains/{name}/contacts returns 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 answers 422 invalid_contact with a fields map naming each field to fix.
  • source object
    Optional. The additional domain fields of your own platform, sent as your platform holds them, for this server to translate into extra_fields. A registrar module generated by this server fills it in: you do not need it when you send extra_fields yourself. A key you send in extra_fields always wins over a translated one.
    5 properties
    • platform string
      Your platform, lower case: whmcs, hostbill.
      • Pattern ^[a-z][a-z0-9-]*$
      • Max length 32
    • version string | null
      Your platform’s own version, e.g. 8.13.1: field names differ between versions.
      • Max length 50
    • module_version string | null
      Version of the registrar module sending the request.
      • Max length 50
    • fields map of string
      The 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.
    • translated array of string
      The extra_fields keys your module translated itself from source.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
  • auth_code string required
    The 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

Responses

  • 200

    Transferred.

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      3 properties
      • state string
        • Value completed
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
  • 202 Accepted — completes later

    Accepted but not completed yet: the registry has taken the request and will finish it later. Your credit has already been debited; the charges stay pending until the operation completes (then they are billed on your next monthly invoice) and are refunded automatically if it fails. Poll GET /reseller/domains/{name} until status leaves pending_registration / pending_transfer (a failed transfer shows transfer.failed: true).

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      3 properties
      • state string
        • Value pending
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
  • 401 unauthenticated ApiError
  • 402 insufficient_credit ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found ApiError
  • 409 operation_in_progress idempotency_in_progress ApiError
  • 422 invalid_request invalid_contact invalid_domain tld_not_sellable invalid_years domain_unavailable domain_not_registered registration_data_missing registrar_error invalid_idempotency_key idempotency_key_reused ApiError
  • 424 registrar_unknown ApiError
  • 429 rate_limited ApiError
  • 500 operation_failed ApiError
  • 503 whois_unavailable ApiError

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());

Response

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.

View in reference

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).

Parameters

Path
  • name string required
Header
  • Idempotency-Key string
    Optional. 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 (with Idempotent-Replayed: true) and never charges twice. The same key with a different request answers 422 idempotency_key_reused; while the first request is still running, 409 idempotency_in_progress (retry after retry_after seconds). Keys are kept for 30 days. See Idempotency in the introduction.
    • Min length 1
    • Max length 255

    See Rate limits and idempotency.

Responses

  • 200

    Restored.

    Idempotent-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      6 properties
      • state string
        • Value completed
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
      • paid_invoice_id integer
      • paid_invoice_covers array of string
      • paid_invoice_covers_truncated boolean
  • 202 Accepted — completes later

    Accepted but not confirmed yet. pending: the registrar has not confirmed the operation; your credit has already been used, and its charges stay pending until the domain’s dates move, then they are billed (poll GET /reseller/domains/{name} and watch expiration_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-Replayed string response header
    true when this response is the replay of an earlier request with the same Idempotency-Key.
    Response body application/json
    • data object
      6 properties
      • state string
        Allowed values pending not_settled
      • domain object
        8 properties
        • id integer
        • name string
        • status string
          Allowed values pending pending_registration pending_transfer active expired grace redemption pending_delete transferring_away transferred_away transfer_failed cancelled fraud
        • registration_date string | null
        • expiration_date string | null
        • next_due_date string | null
        • auto_renew boolean
        • transfer object
          2 properties
          • pending boolean
          • failed boolean
      • charges array of object
        8 properties
        • id integer
        • action string
          Allowed values register transfer renew restore grace_fee redemption_fee
        • years integer
        • currency string
        • net number
        • tax number
        • gross number
        • status string
          Allowed values pending billable invoiced refunded
      • paid_invoice_id integer
      • paid_invoice_covers array of string
      • paid_invoice_covers_truncated boolean
  • 401 unauthenticated ApiError
  • 402 insufficient_credit ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled ApiError
  • 404 client_not_found not_found ApiError
  • 409 renewal_in_progress operation_in_progress idempotency_in_progress ApiError
  • 422 domain_not_restorable registrar_error invalid_idempotency_key idempotency_key_reused ApiError
  • 424 registrar_unknown ApiError
  • 429 rate_limited ApiError

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());

Response

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.

View in reference

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).

Parameters

Path
  • domain integer required
    The domain ID
Body application/json required
  • nameservers array of string required
    2 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

Responses

  • 200
    Response body application/json
    • data object
      2 properties
      • completed boolean
      • message string
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden domain_not_active ApiError
  • 404 client_not_found not_found ApiError
  • 422 invalid_request unprocessable ApiError
  • 429 rate_limited ApiError

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());

Response

200

{
  "data": {
    "completed": true,
    "message": "Nameservers updated."
  }
}

Error responses share one format: see Errors.

View in reference

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.

Parameters
Path
  • name string required
Body application/json required
  • contacts object required
    The roles to change, keyed registrant, admin, tech, billing. Send only roles the domain uses (required_types of GET /reseller/domains/{name}/contacts); any other key answers 422 invalid_contact naming contacts.<role>. Roles you leave out stay as they are.
    4 properties
    • registrant object
      The owner contact, as a full contact (the registrant of POST /reseller/domains/register).
      11 properties
      • first_name string required
        • Max length 100
      • last_name string required
        • Max length 100
      • organization string | null
        Company name. Some TLDs require it and some refuse it: either case answers 422 invalid_contact.
        • Max length 200
      • email string required
        • Format email
        • Max length 200
      • phone string required
        International format +CC.NUMBER, e.g. +39.0212345678. You may instead send a local number here together with registrant.phone_country_code (ISO country, e.g. IT).
        • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
        • Max length 30
      • street1 string required
        • Max length 200
      • street2 string | null
        • Max length 200
      • city string required
        • Max length 100
      • state_province string | null
        • Max length 100
      • postal_code string required
        • Max length 20
      • country_code string required
        ISO 3166-1 alpha-2 country code, e.g. IT.
        • Min length 2
        • Max length 2
        Allowed values AF AL DZ AD AO AI AQ AG SA AR
        240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
    • admin object
      The administrative contact, as a full contact (same fields as the registrant). Organization is always optional here.
      11 properties
      • first_name string required
        • Max length 100
      • last_name string required
        • Max length 100
      • organization string | null
        • Max length 200
      • email string required
        • Format email
        • Max length 200
      • phone string required
        • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
        • Max length 30
      • street1 string required
        • Max length 200
      • street2 string | null
        • Max length 200
      • city string required
        • Max length 100
      • state_province string | null
        • Max length 100
      • postal_code string required
        • Max length 20
      • country_code string required
        • Min length 2
        • Max length 2
        Allowed values AF AL DZ AD AO AI AQ AG SA AR
        240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
    • tech object
      The technical contact, as a full contact.
      11 properties
      • first_name string required
        • Max length 100
      • last_name string required
        • Max length 100
      • organization string | null
        • Max length 200
      • email string required
        • Format email
        • Max length 200
      • phone string required
        • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
        • Max length 30
      • street1 string required
        • Max length 200
      • street2 string | null
        • Max length 200
      • city string required
        • Max length 100
      • state_province string | null
        • Max length 100
      • postal_code string required
        • Max length 20
      • country_code string required
        • Min length 2
        • Max length 2
        Allowed values AF AL DZ AD AO AI AQ AG SA AR
        240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
    • billing object
      The billing contact, as a full contact.
      11 properties
      • first_name string required
        • Max length 100
      • last_name string required
        • Max length 100
      • organization string | null
        • Max length 200
      • email string required
        • Format email
        • Max length 200
      • phone string required
        • Pattern ^\+[0-9]{1,3}\.[0-9]{1,14}$
        • Max length 30
      • street1 string required
        • Max length 200
      • street2 string | null
        • Max length 200
      • city string required
        • Max length 100
      • state_province string | null
        • Max length 100
      • postal_code string required
        • Max length 20
      • country_code string required
        • Min length 2
        • Max length 2
        Allowed values AF AL DZ AD AO AI AQ AG SA AR
        240 more values AM AW AU AT AZ BS BH BD BB BE BZ BJ BM BT BY BO BA BW BR BN BG BF BI KH CM CA CV BQ CZ TD CL CN CY VA CO KM CD CG KP KR CR CI HR CU CW DK DM EC EG SV AE ER EE SZ ET FJ PH FI FR GA GM GE GS DE GH JM JP GI DJ JO GR GD GL GP GU GT GG GN GQ GW GY GF HT HN IN ID IR IQ IE IS BV CX NF IM KY CC CK FK FO HM MP MH UM PN SB TC VI VG AX IL IT JE KZ KE KG KI XK KW LA LS LV LB LR LY LI LT LU MK MG MW MY MV ML MT MA MQ MR MU YT MX FM MD MC MN ME MS MZ MM NA NR NP NI NE NG NU NO NC NZ OM NL PK PW PA PG PY PE PF PL PT PR QA HK MO GB CF DO RE RO RW RU EH KN LC MF VC BL PM WS AS SM SH SN RS SC SL SG SX SY SK SI SO ES LK US SS ZA SD SR SJ SE CH ST TJ TW TZ TF PS IO TH TL TG TK TO TT TN TR TM TV UA UG HU UY UZ VU VE VN WF YE ZM ZW
  • extra_fields map of string
    The registrant’s registry-specific fields: the keys GET /reseller/domains/{name}/contacts returns in extra_fields, which is the authoritative set for an existing domain (it can differ from GET /reseller/tlds/{tld}/extra-fields, which describes a new registration). Only together with contacts.registrant. Fields you do not send keep the value on file. Values are strings: send a checkbox as "1". A missing or invalid field answers 422 invalid_contact naming extra_fields.<key>.
  • source object
    Optional, only together with contacts.registrant. The additional domain fields of your own platform, for this server to translate into extra_fields (see POST /reseller/domains/register).
    5 properties
    • platform string
      Your platform, lower case: whmcs, hostbill.
      • Pattern ^[a-z][a-z0-9-]*$
      • Max length 32
    • version string | null
      Your platform’s own version, e.g. 8.13.1: field names differ between versions.
      • Max length 50
    • module_version string | null
      Version of the registrar module sending the request.
      • Max length 50
    • fields map of string
      The 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.
    • translated array of string
      The extra_fields keys your module translated itself from source.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
Responses
  • 200

    Every role sent is done.

    Response body application/json
    • data object
      3 properties
      • state string
        completed (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 values completed pending
      • results map of string
        One entry per role sent, in the order they were applied: updated, pending, or unchanged (the registry already held exactly this data, so nothing was sent). The same map, with failed and not_attempted, rides on a contact_update_failed or registrant_change_requires_* error so you know which roles were already applied.
      • domain map of any
  • 202 Accepted — completes later

    Accepted; at least one role is waiting on the registry.

    Response body application/json
    • data object
      3 properties
      • state string
        completed (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 values completed pending
      • results map of string
        One entry per role sent, in the order they were applied: updated, pending, or unchanged (the registry already held exactly this data, so nothing was sent). The same map, with failed and not_attempted, rides on a contact_update_failed or registrant_change_requires_* error so you know which roles were already applied.
      • domain map of any
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden not_reseller credit_disabled domain_not_active ApiError
  • 404 client_not_found not_found ApiError
  • 409 registrant_change_requires_trade registrant_change_requires_postal_procedure ApiError
  • 422 invalid_request invalid_contact contact_update_failed contact_update_not_supported ApiError
  • 429 rate_limited ApiError
  • 503 contacts_unavailable ApiError

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());

Response

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.

View in reference

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
  • Requires permission managedomains
  • Rate limit: 10 req/min per user
Parameters
Path
  • domain integer required
    The domain ID
Body application/json required
  • tag string required
    The 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
Responses
  • 200
    Response body application/json
    • data object
      2 properties
      • completed boolean
      • message string
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden domain_not_active ApiError
  • 404 client_not_found not_found ApiError
  • 422 domain_release_not_supported registrar_error invalid_request unprocessable ApiError
  • 429 rate_limited ApiError

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());

Response

200

{
  "data": {
    "completed": true,
    "message": "Your domain has been released to the receiving registrar."
  }
}

Error responses share one format: see Errors.

View in reference

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).

  1. Read the state with GET /client/domains/{id}/dnssec: supported, the record_type the domain takes (ds or dnskey), the operations available now and the accepted algorithms / digest_types.
  2. Turn it on: if enable is in operations, the zone is signed for you — call POST …/dnssec/enable. Otherwise add the records your DNS provider gives you with POST …/dnssec/records, in the form record_type names.
  3. Check the result: every call answers with the refreshed state; while pending is true the 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).

Parameters

Path
  • domain integer required
    The domain ID

Responses

  • 200

    The domain’s DNSSEC state.

    Response body application/json
    • data object
      10 properties
      • supported boolean
        true when DNSSEC can be managed for this domain through this API. false is not an error: DNSSEC is not offered for this domain here, the other fields are then empty and every other DNSSEC call answers dnssec_not_supported.
      • record_type string | null
        The record form this domain takes; null when not supported. 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 with invalid_request.
        Allowed values ds dnskey
      • operations array of string
        The 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 not supported, when the domain is not active (see blocked_reason), and it can be empty while a change is still being applied (pending: true).
        Allowed values add remove disable enable
      • algorithms array of integer
        The algorithm numbers (IANA) this domain accepts in algorithm, as plain integers: 5 RSA/SHA-1, 7 RSASHA1-NSEC3-SHA1, 8 RSA/SHA-256, 10 RSA/SHA-512, 13 ECDSA P-256/SHA-256, 14 ECDSA P-384/SHA-384, 15 Ed25519, 16 Ed448. Often a subset of these. Empty when not supported.
        Allowed values 5 7 8 10 13 14 15 16
      • digest_types array of integer
        ds domains only (absent for dnskey): the digest type numbers accepted in digest_type, as plain integers: 1 SHA-1, 2 SHA-256, 4 SHA-384.
        Allowed values 1 2 4
      • records array of object | null
        The DNSSEC records on file for the domain, each with the fields of the domain’s record_type only. Empty when there is none, and on a zone signed for you, which lists no record (see active). null when 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_reason is then set). A domain in grace, redemption or transferring away is still read.
        7 properties
        • key_tag integer
          ds only: the key tag, 0-65535.
        • flags integer
          dnskey only: 257 (KSK, the usual one) or 256 (ZSK).
        • protocol integer
          dnskey only: always 3.
        • algorithm integer
          Both forms: the DNSSEC algorithm number (IANA), one of the domain’s algorithms.
        • digest_type integer
          ds only: the digest type, one of the domain’s digest_types.
        • digest string
          ds only: the digest, hexadecimal, upper case.
        • public_key string
          dnskey only: the public key, Base64, without whitespace.
      • active boolean | null
        Whether DNSSEC is on for the domain. Usually the same as records not being empty; it also covers a zone signed for you (which lists no record) and DS records set outside this API. null when not supported, or when records is null.
      • pending boolean
        true when a change was accepted but is not applied yet: records and active still show the situation before it. Poll GET …/dnssec until it is false; no need to send the change again.
      • pending_message string | null
        A short customer-readable note about the pending change, when there is one; otherwise null.
      • blocked_reason object | null
        Why no change can be made right now, or null when changes are possible. Set when the domain’s status is not active: DNSSEC is changed only on an active domain, like the nameservers and the contacts. operations is then empty and every change answers 403 domain_not_active.
        2 properties
        • code string
          The code a change would be refused with.
          Allowed values domain_not_active
        • message string
          The reason, readable by a customer.
  • 401 unauthenticated ApiError
  • 403 client_token_required token_not_api_enabled token_not_client_scoped client_access_revoked client_cancelled ip_not_allowed ip_whitelist_misconfigured forbidden ApiError
  • 404 client_not_found not_found ApiError
  • 429 rate_limited ApiError
  • 502 upstream_error ApiError

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());

Response

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.

View in reference

Every DNSSEC call — enable, disable, add and remove records — is in the DNSSEC reference.