Domain Registration
Registrar operations — registering, transferring, and managing TLD registrations and their contacts. This is distinct from Domains, which manages the hostnames attached to WordPress sites.
Feature-flagged
These endpoints are gated by the domain-registration feature flag. When it
is off, every endpoint here returns 503
{"errors":[...],"code":"feature_disabled"}.
Account Scope Required (X-Auth-Account) — a missing header returns 400.
OAuth scopes: reads domains:read, writes domains:write. Mutations require
the can_edit role. A registration with an RTR process already in flight
rejects further mutations with 409 registration_busy. Mutations may
complete synchronously (200) or open an async registrar
process (202 with pending_process). TLDs with no configured
registrar return 503 registrar_unavailable; upstream registrar failures
return 502.
Registering / transferring a domain is an order
The endpoints here check availability (check /
suggestions) and manage existing registrations.
To actually register or transfer a domain, place a paid order via
POST /api/orders/domain (see the full param surface in
Orders). To change a registration's registrant as a paid order,
use POST /api/domain_registrations/:id/registrant_change
(the billable counterpart to PATCH .../holder).
Domain Contacts
Reusable owner/admin/tech/billing contacts that back domain registrations. A
contact is pinned to a single TLD on create because each registry validates a
different set of fields. :id is the numeric contact ID.
Domain Contact shape
- id: Integer
- handle: String
- first_name: String
- last_name: String
- organization: String
- street_address: String
- post_code: String
- city: String
- state: String
- country: String | ISO-2
- email: String
- phone: String | E.164,
+CC.NUMBER - extra_properties: Object | TLD-specific registry fields
- validations: Array
- split_from_id: Integer | null
- created_at: DateTime
- updated_at: DateTime
List Domain Contacts
GET /api/domain_contacts
Params (optional)
- q: String | substring match against handle, organization, full name, or registration name
Returns { "domain_contacts": [ ... ] }.
View a Domain Contact
GET /api/domain_contacts/:id
Create a Domain Contact
POST /api/domain_contacts
Creates the contact at the registrar AND locally. Returns 201. You must
pass tld_id or tld_name so the right registry rules apply. An unknown TLD
returns 404 tld_not_found.
Params
- tld_id: Integer | one of tld_id / tld_name required
- tld_name: String | e.g. ".uk", ".com"
- domain_contact: Object (required)
- first_name, last_name, organization, street_address, post_code, city, state: String
- country: String | ISO-2
- email: String
- phone: String | E.164 (
+CC.NUMBER) - extra_properties: Object | TLD-specific registry fields
Update a Domain Contact
PATCH /api/domain_contacts/:id
Returns 200, or 202 with pending_process when the registry processes the
update asynchronously. If any currently-associated domain rejects the change,
the contact is transparently split (the original keeps the failing domains,
a new contact is created for the rest). Pass tld_id/tld_name to override
which TLD's validation rules apply (defaults to the first linked registration's
TLD).
Params
- tld_id | tld_name: see create
- domain_contact: Object | same fields as create (all optional)
Delete a Domain Contact
DELETE /api/domain_contacts/:id
Only allowed when no registration uses this contact. Returns 204.
Errors
- 422
contact_in_use| still linked to a registration - 422
tld_required_for_orphan| orphan contact + no tld_id/tld_name provided
Resend Verification Email
POST /api/domain_contacts/:id/resend_verification
For contacts on TLDs that require registrant validation (.au, .it, …). Returns
{ "status": "queued" }.
Domain Registrations
:id is the registration guid.
Domain Registration shape
- id: String | guid
- name: String
- status: String
- expires_at: DateTime
- auto_renew: Boolean
- locked: Boolean
- privacy_protect: Boolean
- dnssec_enabled: Boolean
- server_transfer_prohibited: Boolean
- local_contact_active: Boolean
- cancellation_deadline_at: DateTime | when applicable
- within_cancellation_window: Boolean | when applicable
- nameservers:
Array<String> - tld: Object | { id, name, registrar }
- owner_contact / admin_contact / tech_contact / billing_contact: Object | see Domain Contact shape, or null
- pending_async: Boolean
- rtr_process_id: String
- rtr_process_status: String
- transfer_out_pending: Boolean
- push_transfer_pending: Boolean
- subscription_id: String
- feature_flags: Object | { supports_privacy, supports_transfer_lock, supports_push_transfer, supports_dnssec, requires_contact_validation, authcode_behavior }
- fees: Object | { privacy_enable, registrant_change } — each
{ amount_cents, currency }or null - created_at: DateTime
- updated_at: DateTime
List Domain Registrations
GET /api/domain_registrations
Params (optional)
- q: String | case-insensitive substring match against
name
Returns { "domain_registrations": [ ... ] }.
View a Domain Registration
GET /api/domain_registrations/:id
Adds, when applicable:
Returned Params
- domain_registration: Object | see shape above
- dns_zone: Object | { id, name } if the account has a DnsZone for this name; else null
- linked_site: Object | { id, name } if a Site uses this name as its hostname; else null
- transfer_out: Object | live registry view, only while
transfer_out_pendingis true
Check Availability
POST /api/domain_registrations/check
Params
- domain: String (required) | fully-qualified, e.g. "example.com"
Returned Params
- domain: String | normalized lookup
- available: Boolean | true only when the registry says it's available AND a
create_priceexists for this account - premium: Boolean
- owned_by_account: Boolean
- reason: String | null | registrar-supplied reason when unavailable
- create_price: Object | null |
{ amount_cents, currency }— current orderable registration price - transfer_price: Object | null | orderable transfer-in price (set when unavailable)
Errors
- 422
missing_domain/invalid_domain/unknown_tld - 502
registrar_unavailable
Domain Suggestions
POST /api/domain_registrations/suggestions
Params
- domain: String (required) | base name or fully-qualified
Returned Params
- domain: String | normalized lookup
- suggestions:
Array<Object>- domain: String
- tld: String
- source: String | ADAC categorization (e.g. "spinner", "premium")
- available: Boolean
- price: Object |
{ amount_cents, currency }
Errors
- 422
missing_domain - 503
missing_api_key - 502
registrar_unavailable
Update a Registration
PATCH /api/domain_registrations/:id
Currently the only supported field is auto_renew. Toggling it keeps Stripe
billing in sync and refunds within the TLD's cancellation window where
applicable.
Params
- domain_registration: Object
- auto_renew: Boolean
Returned Params
- domain_registration: Object | refreshed
- scenario: String |
enabled,clean_cancel,refund_cancel, orlate_cancel
Errors
- 422
cancellation_window_passed| cannot disable auto-renew after the window - 400
no_op| no supported fields in the update
Update Nameservers
PATCH /api/domain_registrations/:id/nameservers
Params
- nameservers:
Array<String>(required)
Returns 200 or 202 (with pending_process).
Errors
- 422
nameservers_required| the list is empty / all blank
Toggle Transfer Lock
PATCH /api/domain_registrations/:id/lock
Toggles CLIENT_TRANSFER_PROHIBITED at the registry.
Params
- locked: Boolean (required)
Errors
- 422
lock_unsupported| TLD does not support a transfer lock - 422
server_transfer_prohibited| registry has prohibited transfers
Toggle WHOIS Privacy
PATCH /api/domain_registrations/:id/privacy
Params
- privacy_protect: Boolean (required)
Disabling privacy is always free. Enabling on a metered TLD returns 402:
Fee-bearing response (402 Payment Required)
- errors: Array
- code: String |
payment_required - price: Object | { amount_cents, currency, role: "privacy" }
- portal_url: String
- remediation: String
Errors
- 422
privacy_unsupported| TLD does not support WHOIS privacy
Manage DNSSEC
PATCH /api/domain_registrations/:id/dnssec
Params
- mode: String (required) |
enable,disable, oradd_keys - key_data:
Array<Object>| required for enable / add_keys- flags: Integer
- algorithm: Integer
- publicKey: String | base64
Errors
- 422
invalid_mode| mode missing or unknown - 422
dnssec_no_keys| enable/add_keys called without key_data
Change Registrant / Holder
PATCH /api/domain_registrations/:id/holder
Either edit the existing owner contact in place, or replace it.
Params
- holder_contact: Object (required)
- action_mode: String |
edit_currentorchange - first_name, last_name, organization, street_address, post_code, city, state: String
- country: String | ISO-2
- email: String
- phone: String | E.164
- extra_properties: Object
- confirm_reverification: "1" | required when edits touch a validated field
- action_mode: String |
- source_domain_contact_id: Integer | with
change, reuse this existing contact - new_contact: Object | with
change, create a new contact
The fee gate fires only on action_mode: change on a metered TLD (returns
402 payment_required with a price block whose role is registrant_change
+ portal_url); edit_current edits are always free. To perform the billable
change directly (rather than being bounced to the portal), use
POST .../registrant_change.
Errors
- 402
payment_required|action_mode: changeon a TLD that meters registrant changes (pricerole: "registrant_change", fee block +portal_url) - 409
reverification_required| validated fields changed withoutconfirm_reverification: "1" - 404
source_contact_not_found|source_domain_contact_idnot found or unused in this account
Order a Registrant (Holder) Change
POST /api/domain_registrations/:id/registrant_change
Places a paid order to change the registration's registrant (owner) contact —
for TLDs that bill registrant changes (e.g. .com / .net via Verisign IRTP). This
is the billable counterpart to PATCH .../holder:
that endpoint makes free edits and bounces fee-bearing changes to the portal with
402; this one charges the account's default payment method off-session and
applies the change asynchronously. Returns the same 202 Accepted order/cart
envelope as POST /api/orders — poll the returned order via
/api/orders/:id.
OAuth-blocked; needs an order-create user
Not in the domains:write allowlist — OAuth access tokens cannot reach it.
Requires a user-scoped credential (orders are owned by a User): a system
Account-bearer key returns 401 user_required, and a user lacking
order-create permission returns 403 forbidden. Like other mutations, an
in-flight RTR process rejects with 409 registration_busy.
Params (provide exactly ONE new-owner source)
- source_domain_contact_id: Integer | reuse an existing contact owned by the account
- new_contact: Object | create a new owner contact
- first_name, last_name, country (ISO-2), email, phone (E.164
+CC.NUMBER): required - organization, street_address, post_code, city, state: String | optional
- extra_properties: Object | optional, TLD-specific registry fields
- first_name, last_name, country (ISO-2), email, phone (E.164
Returns 202 with the order/cart envelope.
Errors
- 401
user_required| system Account-bearer key (no user) - 403
forbidden| user lacks order-create permission - 403
trial_account| trial accounts cannot order - 409
registration_busy| an RTR process is already in flight - 422
source_contact_not_found|source_domain_contact_idnot found (note:422here vs404on/holder) - 422
contact_invalid|new_contactmissing required fields or registry-rejected (includesviolations) - 422
no_registrant_change_price| no registrant-change price configured for this account/TLD - 422
cart_pay_failed| payment could not be initiated (any contact created for the order is torn down)
Update Role Contacts
PATCH /api/domain_registrations/:id/contacts
Set admin / tech / billing role contacts (the holder is changed via /holder).
Each role is independently optional. For each present role choose one source:
{role}_contact: { source: "same_as_owner" }{role}_contact: { source: "existing" }+{role}_source_domain_contact_id: <id>{role}_contact: { source: "new" }+{role}_new_contact: { ...fields... }
Errors
- 422
owner_contact_missing|same_as_ownerused with no owner contact set
Retrieve the EPP / Auth Code
GET /api/domain_registrations/:id/epp_code
Requires the domains:write scope despite being a GET. Returns
{ "authcode": "..." }.
Errors
- 422
server_transfer_prohibited| registry has prohibited transfers - 422
authcode_unavailable| TLD hides the authcode (authcode_behavior: hidden) - 502
authcode_unavailable| registrar failed to return the authcode
Force Re-Sync
POST /api/domain_registrations/:id/force_sync
Re-reads the domain from the registrar and syncs local state.
Returned Params
- domain_registration: Object | refreshed
- changed:
Array<String>| field names that changed
Resolve a Pending Outbound Transfer
POST /api/domain_registrations/:id/transfer_out
Params
- decision: String (required) |
approveorreject
Errors
- 422
no_pending_transfer_out - 422
invalid_decision
Push Transfer to Another Provider
POST /api/domain_registrations/:id/push_transfer
Registrar-to-registrar push transfer (no authcode). Returns 200 or 202.
Params
- recipient: String (required) | destination registrar handle (e.g. "REG-IDS")
Errors
- 422
push_transfer_unsupported/push_transfer_in_progress/domain_not_transferable
Resend Holder Contact Verification
POST /api/domain_registrations/:id/resend_contact_verification
For TLDs requiring registrant contact verification. Returns
{ "status": "queued" }.
Errors
- 422
verification_not_applicable| TLD doesn't require validation, or no owner contact
Hosts (Glue Records)
Glue records for a registration. Nested under
/api/domain_registrations/:domain_registration_id/hosts. The route param is
:host_name.
List Hosts
GET .../hosts
Returned Params
- hosts:
Array<Object>- host_name: String | e.g. "ns1.example.com"
- addresses:
Array<Object>- address: String | IP literal
- version: Integer | 4 or 6
Create a Host
POST .../hosts
The hostname must be a subdomain of the registration. Each address is
auto-classified as IPv4/IPv6. Returns 201 { "host": { ... } }.
Params
- host_name: String (required) | fully-qualified subdomain
- addresses:
Array<Object>(required, at least one)- address: String | IP literal (no CIDR)
Update a Host's Addresses
PATCH .../hosts/:host_name
Replaces the address set. Returns 200.
Params
- addresses:
Array<Object>(required) | same shape as create
Delete a Host
DELETE .../hosts/:host_name
Returns 204.
Processes
RTR processes attached to a registration — every async registrar operation opens
one. Poll these after a 202. Nested under
/api/domain_registrations/:domain_registration_id/processes.
List Processes
GET .../processes
Returns { "processes": [ ... ] }, newest first (raw RTR process records).
Get a Process
GET .../processes/:id
Returned Params
- process: Object | raw RTR process body
- log:
Array<Object>
A process whose identifier doesn't belong to the registration returns 404.
Resume a Suspended Process
POST .../processes/:id/resend
Returns { "status": "queued" }.
Cancel a Process
DELETE .../processes/:id/cancel
Returns { "status": "cancelled" }.