Quick Answer
Domain registrar APIs allow you to automate domain registration, DNS management, renewal, and transfers programmatically. GoDaddy, Namecheap, Cloudflare, and ResellerClub all offer REST APIs with varying access requirements. GoDaddy requires 50+ domains in your account (as of May 2024), Namecheap requires $50+ balance or 20+ domains, Cloudflare offers free API access for all users, and ResellerClub provides reseller-focused APIs with sandbox testing. Authentication typically uses API keys with rate limits of 60-100 requests per minute.
Table of Contents
- Introduction to Domain APIs
- API Access Requirements by Registrar
- GoDaddy API Integration
- Namecheap API Integration
- Cloudflare Registrar API
- ResellerClub API Integration
- Common Automation Workflows
- Error Handling and Retries
- Rate Limiting Strategies
- Security Best Practices
- Building a Multi-Registrar Wrapper
- Testing and Sandbox Environments
- Webhooks and Event Notifications
- Frequently Asked Questions
- Key Takeaways
- Next Steps
- Research Sources
Introduction to Domain APIs
Domain registrar APIs enable programmatic control over domain operations that would otherwise require manual dashboard interactions. These APIs are essential for businesses managing large domain portfolios, domain resellers, and developers building domain-integrated applications.
Why Automate Domain Management?
Common automation use cases:
- Bulk Operations - Register, renew, or transfer hundreds of domains simultaneously
- DNS Automation - Update DNS records as part of CI/CD pipelines
- Domain Portfolio Management - Monitor expiration dates, auto-renew selectively
- Reseller Platforms - Build white-label domain registration services
- Infrastructure as Code - Manage DNS alongside cloud infrastructure
- Monitoring and Alerts - Track domain status changes automatically
Overview of Registrar API Capabilities
| Feature | GoDaddy | Namecheap | Cloudflare | ResellerClub |
|---|---|---|---|---|
| Domain Registration | Yes | Yes | Limited* | Yes |
| Domain Transfer | Yes | Yes | Yes | Yes |
| DNS Management | Yes | Yes | Yes (Zones API) | Yes |
| WHOIS Updates | Yes | Yes | Yes | Yes |
| Domain Renewal | Yes | Yes | Auto-only | Yes |
| Availability Check | Yes | Yes | No** | Yes |
| Bulk Operations | Yes | Yes | Yes | Yes |
| Sandbox Environment | Yes (OTE) | Yes | No | Yes |
*Cloudflare registration via API is Enterprise-only **Cloudflare is primarily for transfers and existing domain management
API Access Requirements by Registrar
Before writing code, understand each registrar's access requirements:
GoDaddy API Requirements (Updated May 2024)
Access restrictions:
- 50+ domains required in your account for production API access
- Prior to May 2024, only 10+ domains were required
- Good as Gold account needed for purchases (prepaid balance)
- Separate OTE (test) and production environments
Rate limits:
- 60 requests per minute per endpoint
- Stricter limits on availability checks
Namecheap API Requirements
Access prerequisites (one of the following):
- Account balance of $50+
- 20+ domains in your account
- Purchases totaling $50+ within the last 2 years
Additional requirements:
- IP address whitelisting required
- Separate API key generation
- Sandbox environment available
Cloudflare API Requirements
Access:
- Free API access for all accounts
- API Token or Global API Key authentication
- Full DNS/zone management for all users
- Domain registration endpoints require Enterprise plan
Rate limits:
- 1,200 requests per 5 minutes per user
- Higher limits available for Enterprise
ResellerClub API Requirements
Access:
- Reseller account required
- IP address whitelisting mandatory
- Test mode available for development
- WHMCS integration available
GoDaddy API Integration
Authentication Setup
GoDaddy uses a simple API key and secret authentication scheme:
JavaScript/Node.js:
const axios = require('axios');
class GoDaddyAPI {
constructor(apiKey, apiSecret, isProduction = false) {
this.baseUrl = isProduction
? 'https://api.godaddy.com'
: 'https://api.ote-godaddy.com';
this.headers = {
'Authorization': `sso-key ${apiKey}:${apiSecret}`,
'Content-Type': 'application/json'
};
}
async getDomainInfo(domain) {
try {
const response = await axios.get(
`${this.baseUrl}/v1/domains/${domain}`,
{ headers: this.headers }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async listDomains(limit = 100, marker = '') {
try {
const params = new URLSearchParams({ limit });
if (marker) params.append('marker', marker);
const response = await axios.get(
`${this.baseUrl}/v1/domains?${params}`,
{ headers: this.headers }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async checkAvailability(domain) {
try {
const response = await axios.get(
`${this.baseUrl}/v1/domains/available?domain=${domain}`,
{ headers: this.headers }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
handleError(error) {
if (error.response) {
const { status, data } = error.response;
throw new Error(`GoDaddy API Error ${status}: ${JSON.stringify(data)}`);
}
throw error;
}
}
// Usage
const godaddy = new GoDaddyAPI('your-api-key', 'your-api-secret', true);
const domains = await godaddy.listDomains();
Python:
import requests
from typing import Optional, Dict, List, Any
class GoDaddyAPI:
def __init__(self, api_key: str, api_secret: str, production: bool = False):
self.base_url = (
'https://api.godaddy.com' if production
else 'https://api.ote-godaddy.com'
)
self.headers = {
'Authorization': f'sso-key {api_key}:{api_secret}',
'Content-Type': 'application/json'
}
def get_domain_info(self, domain: str) -> Dict[str, Any]:
"""Retrieve details for a specific domain."""
response = requests.get(
f'{self.base_url}/v1/domains/{domain}',
headers=self.headers
)
response.raise_for_status()
return response.json()
def list_domains(self, limit: int = 100, marker: str = '') -> List[Dict]:
"""List all domains in account."""
params = {'limit': limit}
if marker:
params['marker'] = marker
response = requests.get(
f'{self.base_url}/v1/domains',
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json()
def check_availability(self, domain: str) -> Dict[str, Any]:
"""Check if a domain is available for registration."""
response = requests.get(
f'{self.base_url}/v1/domains/available',
headers=self.headers,
params={'domain': domain}
)
response.raise_for_status()
return response.json()
def update_dns_records(self, domain: str, record_type: str,
records: List[Dict]) -> None:
"""Update DNS records for a domain."""
response = requests.put(
f'{self.base_url}/v1/domains/{domain}/records/{record_type}',
headers=self.headers,
json=records
)
response.raise_for_status()
def renew_domain(self, domain: str, years: int = 1) -> Dict[str, Any]:
"""Renew a domain registration."""
response = requests.post(
f'{self.base_url}/v1/domains/{domain}/renew',
headers=self.headers,
json={'period': years}
)
response.raise_for_status()
return response.json()
# Usage example
godaddy = GoDaddyAPI('your-api-key', 'your-api-secret', production=True)
# Get domain info
domain_info = godaddy.get_domain_info('example.com')
print(f"Domain expires: {domain_info['expires']}")
# Check availability
availability = godaddy.check_availability('newdomain.com')
print(f"Available: {availability['available']}")
GoDaddy DNS Management
// Update A records
async function updateARecord(domain, ip) {
const records = [{
data: ip,
name: '@',
ttl: 3600,
type: 'A'
}];
await axios.put(
`${baseUrl}/v1/domains/${domain}/records/A/@`,
records,
{ headers }
);
}
// Add CNAME record
async function addCnameRecord(domain, name, target) {
const records = [{
data: target,
name: name,
ttl: 3600,
type: 'CNAME'
}];
await axios.patch(
`${baseUrl}/v1/domains/${domain}/records`,
records,
{ headers }
);
}
// Get all DNS records
async function getAllRecords(domain) {
const response = await axios.get(
`${baseUrl}/v1/domains/${domain}/records`,
{ headers }
);
return response.data;
}
Namecheap API Integration
Namecheap uses XML-based API responses (though requests are query parameters). Here's how to work with it effectively:
Authentication and Setup
const axios = require('axios');
const xml2js = require('xml2js');
class NamecheapAPI {
constructor(apiUser, apiKey, username, clientIp, sandbox = true) {
this.baseUrl = sandbox
? 'https://api.sandbox.namecheap.com/xml.response'
: 'https://api.namecheap.com/xml.response';
this.apiUser = apiUser;
this.apiKey = apiKey;
this.username = username;
this.clientIp = clientIp;
this.parser = new xml2js.Parser({ explicitArray: false });
}
async makeRequest(command, params = {}) {
const queryParams = new URLSearchParams({
ApiUser: this.apiUser,
ApiKey: this.apiKey,
UserName: this.username,
ClientIp: this.clientIp,
Command: command,
...params
});
const response = await axios.get(`${this.baseUrl}?${queryParams}`);
const result = await this.parser.parseStringPromise(response.data);
if (result.ApiResponse.$.Status === 'ERROR') {
const errors = result.ApiResponse.Errors.Error;
throw new Error(`Namecheap Error: ${JSON.stringify(errors)}`);
}
return result.ApiResponse.CommandResponse;
}
async checkDomainAvailability(domains) {
// Can check multiple domains, comma-separated
const domainList = Array.isArray(domains) ? domains.join(',') : domains;
const result = await this.makeRequest('namecheap.domains.check', {
DomainList: domainList
});
return result.DomainCheckResult;
}
async getDomainInfo(domain) {
const result = await this.makeRequest('namecheap.domains.getInfo', {
DomainName: domain
});
return result.DomainGetInfoResult;
}
async listDomains(page = 1, pageSize = 100) {
const result = await this.makeRequest('namecheap.domains.getList', {
Page: page,
PageSize: pageSize
});
return result.DomainGetListResult;
}
async getDnsHosts(sld, tld) {
const result = await this.makeRequest('namecheap.domains.dns.getHosts', {
SLD: sld,
TLD: tld
});
return result.DomainDNSGetHostsResult;
}
async setDnsHosts(sld, tld, hosts) {
// hosts should be an array of {HostName, RecordType, Address, TTL}
const params = { SLD: sld, TLD: tld };
hosts.forEach((host, index) => {
const i = index + 1;
params[`HostName${i}`] = host.HostName;
params[`RecordType${i}`] = host.RecordType;
params[`Address${i}`] = host.Address;
params[`TTL${i}`] = host.TTL || 1800;
});
return await this.makeRequest('namecheap.domains.dns.setHosts', params);
}
}
// Usage
const namecheap = new NamecheapAPI(
'your-api-user',
'your-api-key',
'your-username',
'123.45.67.89', // Your whitelisted IP
false // production
);
// Check multiple domains
const availability = await namecheap.checkDomainAvailability([
'example1.com',
'example2.net',
'example3.org'
]);
Python:
import requests
import xml.etree.ElementTree as ET
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class DnsRecord:
host_name: str
record_type: str
address: str
ttl: int = 1800
class NamecheapAPI:
def __init__(self, api_user: str, api_key: str, username: str,
client_ip: str, sandbox: bool = True):
self.base_url = (
'https://api.sandbox.namecheap.com/xml.response' if sandbox
else 'https://api.namecheap.com/xml.response'
)
self.api_user = api_user
self.api_key = api_key
self.username = username
self.client_ip = client_ip
def _make_request(self, command: str, **params) -> ET.Element:
"""Make a request to Namecheap API."""
query_params = {
'ApiUser': self.api_user,
'ApiKey': self.api_key,
'UserName': self.username,
'ClientIp': self.client_ip,
'Command': command,
**params
}
response = requests.get(self.base_url, params=query_params)
response.raise_for_status()
root = ET.fromstring(response.content)
# Check for errors
if root.attrib.get('Status') == 'ERROR':
errors = root.find('.//Errors')
error_msgs = [e.text for e in errors.findall('Error')]
raise Exception(f"Namecheap API Error: {error_msgs}")
return root.find('CommandResponse')
def check_availability(self, domains: List[str]) -> List[Dict[str, Any]]:
"""Check availability of one or more domains."""
domain_list = ','.join(domains)
response = self._make_request(
'namecheap.domains.check',
DomainList=domain_list
)
results = []
for result in response.findall('DomainCheckResult'):
results.append({
'domain': result.attrib['Domain'],
'available': result.attrib['Available'] == 'true',
'premium': result.attrib.get('IsPremiumName') == 'true',
'price': result.attrib.get('PremiumRegistrationPrice')
})
return results
def get_domain_info(self, domain: str) -> Dict[str, Any]:
"""Get detailed information about a domain."""
response = self._make_request(
'namecheap.domains.getInfo',
DomainName=domain
)
result = response.find('DomainGetInfoResult')
return {
'domain': result.attrib['DomainName'],
'created': result.find('DomainDetails/CreatedDate').text,
'expires': result.find('DomainDetails/ExpiredDate').text,
'locked': result.attrib.get('IsLocked') == 'true',
'auto_renew': result.attrib.get('AutoRenew') == 'true',
'whois_guard': result.find('Whoisguard/Enabled').text == 'True'
}
def list_domains(self, page: int = 1, page_size: int = 100) -> List[Dict]:
"""List all domains in the account."""
response = self._make_request(
'namecheap.domains.getList',
Page=str(page),
PageSize=str(page_size)
)
domains = []
for domain in response.findall('.//Domain'):
domains.append({
'name': domain.attrib['Name'],
'expires': domain.attrib['Expires'],
'auto_renew': domain.attrib['AutoRenew'] == 'true',
'locked': domain.attrib['IsLocked'] == 'true'
})
return domains
def set_dns_records(self, sld: str, tld: str,
records: List[DnsRecord]) -> bool:
"""Set DNS records for a domain."""
params = {'SLD': sld, 'TLD': tld}
for i, record in enumerate(records, 1):
params[f'HostName{i}'] = record.host_name
params[f'RecordType{i}'] = record.record_type
params[f'Address{i}'] = record.address
params[f'TTL{i}'] = str(record.ttl)
self._make_request('namecheap.domains.dns.setHosts', **params)
return True
# Usage example
namecheap = NamecheapAPI(
api_user='your-api-user',
api_key='your-api-key',
username='your-username',
client_ip='123.45.67.89',
sandbox=False
)
# Check availability
results = namecheap.check_availability(['example.com', 'test.net'])
for r in results:
status = 'Available' if r['available'] else 'Taken'
print(f"{r['domain']}: {status}")
# Update DNS records
records = [
DnsRecord('@', 'A', '192.168.1.1', 3600),
DnsRecord('www', 'CNAME', 'example.com', 3600),
DnsRecord('@', 'MX', 'mail.example.com', 3600)
]
namecheap.set_dns_records('example', 'com', records)
Cloudflare Registrar API
Cloudflare's API is excellent for DNS management but has limitations for domain registration (Enterprise-only). However, it excels at zone and DNS record management.
Authentication
Cloudflare supports both API Tokens (recommended) and Global API Key:
const axios = require('axios');
class CloudflareAPI {
constructor(apiToken, accountId) {
this.baseUrl = 'https://api.cloudflare.com/client/v4';
this.accountId = accountId;
this.headers = {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
};
}
async listDomains() {
const response = await axios.get(
`${this.baseUrl}/accounts/${this.accountId}/registrar/domains`,
{ headers: this.headers }
);
return response.data.result;
}
async getDomainInfo(domain) {
const response = await axios.get(
`${this.baseUrl}/accounts/${this.accountId}/registrar/domains/${domain}`,
{ headers: this.headers }
);
return response.data.result;
}
async updateDomainSettings(domain, settings) {
const response = await axios.put(
`${this.baseUrl}/accounts/${this.accountId}/registrar/domains/${domain}`,
settings,
{ headers: this.headers }
);
return response.data.result;
}
// Zone/DNS Management (available to all users)
async listZones() {
const response = await axios.get(
`${this.baseUrl}/zones`,
{ headers: this.headers }
);
return response.data.result;
}
async getZoneByName(domain) {
const response = await axios.get(
`${this.baseUrl}/zones?name=${domain}`,
{ headers: this.headers }
);
return response.data.result[0];
}
async listDnsRecords(zoneId) {
const response = await axios.get(
`${this.baseUrl}/zones/${zoneId}/dns_records`,
{ headers: this.headers }
);
return response.data.result;
}
async createDnsRecord(zoneId, record) {
const response = await axios.post(
`${this.baseUrl}/zones/${zoneId}/dns_records`,
record,
{ headers: this.headers }
);
return response.data.result;
}
async updateDnsRecord(zoneId, recordId, record) {
const response = await axios.put(
`${this.baseUrl}/zones/${zoneId}/dns_records/${recordId}`,
record,
{ headers: this.headers }
);
return response.data.result;
}
async deleteDnsRecord(zoneId, recordId) {
await axios.delete(
`${this.baseUrl}/zones/${zoneId}/dns_records/${recordId}`,
{ headers: this.headers }
);
return true;
}
}
// Usage
const cloudflare = new CloudflareAPI('your-api-token', 'your-account-id');
// Manage DNS records
async function updateWebsiteRecords(domain, newIp) {
const zone = await cloudflare.getZoneByName(domain);
const records = await cloudflare.listDnsRecords(zone.id);
// Find and update A record
const aRecord = records.find(r => r.type === 'A' && r.name === domain);
if (aRecord) {
await cloudflare.updateDnsRecord(zone.id, aRecord.id, {
type: 'A',
name: domain,
content: newIp,
ttl: 1, // Auto TTL
proxied: true
});
}
}
Python:
import requests
from typing import List, Dict, Any, Optional
class CloudflareAPI:
def __init__(self, api_token: str, account_id: str):
self.base_url = 'https://api.cloudflare.com/client/v4'
self.account_id = account_id
self.headers = {
'Authorization': f'Bearer {api_token}',
'Content-Type': 'application/json'
}
def list_domains(self) -> List[Dict[str, Any]]:
"""List domains registered through Cloudflare Registrar."""
response = requests.get(
f'{self.base_url}/accounts/{self.account_id}/registrar/domains',
headers=self.headers
)
response.raise_for_status()
return response.json()['result']
def get_domain_info(self, domain: str) -> Dict[str, Any]:
"""Get registrar details for a domain."""
response = requests.get(
f'{self.base_url}/accounts/{self.account_id}/registrar/domains/{domain}',
headers=self.headers
)
response.raise_for_status()
return response.json()['result']
def list_zones(self) -> List[Dict[str, Any]]:
"""List all DNS zones in the account."""
response = requests.get(
f'{self.base_url}/zones',
headers=self.headers
)
response.raise_for_status()
return response.json()['result']
def get_zone_by_name(self, domain: str) -> Optional[Dict[str, Any]]:
"""Get zone ID for a domain."""
response = requests.get(
f'{self.base_url}/zones',
headers=self.headers,
params={'name': domain}
)
response.raise_for_status()
results = response.json()['result']
return results[0] if results else None
def list_dns_records(self, zone_id: str,
record_type: str = None) -> List[Dict]:
"""List DNS records for a zone."""
params = {}
if record_type:
params['type'] = record_type
response = requests.get(
f'{self.base_url}/zones/{zone_id}/dns_records',
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json()['result']
def create_dns_record(self, zone_id: str, record_type: str,
name: str, content: str, ttl: int = 1,
proxied: bool = False) -> Dict[str, Any]:
"""Create a new DNS record."""
response = requests.post(
f'{self.base_url}/zones/{zone_id}/dns_records',
headers=self.headers,
json={
'type': record_type,
'name': name,
'content': content,
'ttl': ttl,
'proxied': proxied
}
)
response.raise_for_status()
return response.json()['result']
def update_dns_record(self, zone_id: str, record_id: str,
record_type: str, name: str, content: str,
ttl: int = 1, proxied: bool = False) -> Dict:
"""Update an existing DNS record."""
response = requests.put(
f'{self.base_url}/zones/{zone_id}/dns_records/{record_id}',
headers=self.headers,
json={
'type': record_type,
'name': name,
'content': content,
'ttl': ttl,
'proxied': proxied
}
)
response.raise_for_status()
return response.json()['result']
def delete_dns_record(self, zone_id: str, record_id: str) -> bool:
"""Delete a DNS record."""
response = requests.delete(
f'{self.base_url}/zones/{zone_id}/dns_records/{record_id}',
headers=self.headers
)
response.raise_for_status()
return True
# Usage example
cf = CloudflareAPI('your-api-token', 'your-account-id')
# Update website IP
zone = cf.get_zone_by_name('example.com')
records = cf.list_dns_records(zone['id'], 'A')
for record in records:
if record['name'] == 'example.com':
cf.update_dns_record(
zone['id'],
record['id'],
'A',
'example.com',
'203.0.113.50',
ttl=300,
proxied=True
)
ResellerClub API Integration
ResellerClub provides reseller-focused APIs ideal for building domain registration platforms:
Authentication and Setup
const axios = require('axios');
class ResellerClubAPI {
constructor(resellerId, apiKey, testMode = true) {
this.baseUrl = testMode
? 'https://test.httpapi.com/api'
: 'https://httpapi.com/api';
this.resellerId = resellerId;
this.apiKey = apiKey;
}
getAuthParams() {
return {
'auth-userid': this.resellerId,
'api-key': this.apiKey
};
}
async checkAvailability(domains, tlds) {
const params = new URLSearchParams({
...this.getAuthParams()
});
// Add multiple domain-name params
domains.forEach(d => params.append('domain-name', d));
tlds.forEach(t => params.append('tlds', t));
const response = await axios.get(
`https://domaincheck.httpapi.com/api/domains/available.json?${params}`
);
return response.data;
}
async getDomainDetails(orderId) {
const params = new URLSearchParams({
...this.getAuthParams(),
'order-id': orderId
});
const response = await axios.get(
`${this.baseUrl}/domains/details.json?${params}`
);
return response.data;
}
async searchDomainByName(domain) {
const params = new URLSearchParams({
...this.getAuthParams(),
'domain-name': domain,
'no-of-records': '10',
'page-no': '1'
});
const response = await axios.get(
`${this.baseUrl}/domains/search.json?${params}`
);
return response.data;
}
async registerDomain(domainName, years, nameservers, contacts) {
const params = new URLSearchParams({
...this.getAuthParams(),
'domain-name': domainName,
'years': years,
'customer-id': contacts.customerId,
'reg-contact-id': contacts.registrant,
'admin-contact-id': contacts.admin,
'tech-contact-id': contacts.tech,
'billing-contact-id': contacts.billing,
'invoice-option': 'KeepInvoice'
});
nameservers.forEach(ns => params.append('ns', ns));
const response = await axios.post(
`${this.baseUrl}/domains/register.json`,
params
);
return response.data;
}
async modifyNameservers(orderId, nameservers) {
const params = new URLSearchParams({
...this.getAuthParams(),
'order-id': orderId
});
nameservers.forEach(ns => params.append('ns', ns));
const response = await axios.post(
`${this.baseUrl}/domains/modify-ns.json`,
params
);
return response.data;
}
async renewDomain(orderId, years, expDate) {
const params = new URLSearchParams({
...this.getAuthParams(),
'order-id': orderId,
'years': years,
'exp-date': expDate, // Current expiry timestamp
'invoice-option': 'KeepInvoice'
});
const response = await axios.post(
`${this.baseUrl}/domains/renew.json`,
params
);
return response.data;
}
}
// Usage
const resellerclub = new ResellerClubAPI('your-reseller-id', 'your-api-key', false);
// Check availability across multiple TLDs
const availability = await resellerclub.checkAvailability(
['example', 'mysite'],
['com', 'net', 'org']
);
Python:
import requests
from typing import List, Dict, Any
class ResellerClubAPI:
def __init__(self, reseller_id: str, api_key: str, test_mode: bool = True):
self.base_url = (
'https://test.httpapi.com/api' if test_mode
else 'https://httpapi.com/api'
)
self.reseller_id = reseller_id
self.api_key = api_key
def _get_auth_params(self) -> Dict[str, str]:
return {
'auth-userid': self.reseller_id,
'api-key': self.api_key
}
def check_availability(self, domains: List[str],
tlds: List[str]) -> Dict[str, Any]:
"""Check domain availability across multiple TLDs."""
params = list(self._get_auth_params().items())
# Add multiple domain-name and tlds params
for domain in domains:
params.append(('domain-name', domain))
for tld in tlds:
params.append(('tlds', tld))
response = requests.get(
'https://domaincheck.httpapi.com/api/domains/available.json',
params=params
)
response.raise_for_status()
return response.json()
def get_domain_details(self, order_id: str) -> Dict[str, Any]:
"""Get details of a domain order."""
params = {
**self._get_auth_params(),
'order-id': order_id
}
response = requests.get(
f'{self.base_url}/domains/details.json',
params=params
)
response.raise_for_status()
return response.json()
def search_domain(self, domain_name: str) -> Dict[str, Any]:
"""Search for a domain in the account."""
params = {
**self._get_auth_params(),
'domain-name': domain_name,
'no-of-records': '10',
'page-no': '1'
}
response = requests.get(
f'{self.base_url}/domains/search.json',
params=params
)
response.raise_for_status()
return response.json()
def register_domain(self, domain_name: str, years: int,
nameservers: List[str],
contacts: Dict[str, str]) -> Dict[str, Any]:
"""Register a new domain."""
params = list({
**self._get_auth_params(),
'domain-name': domain_name,
'years': str(years),
'customer-id': contacts['customer_id'],
'reg-contact-id': contacts['registrant'],
'admin-contact-id': contacts['admin'],
'tech-contact-id': contacts['tech'],
'billing-contact-id': contacts['billing'],
'invoice-option': 'KeepInvoice'
}.items())
for ns in nameservers:
params.append(('ns', ns))
response = requests.post(
f'{self.base_url}/domains/register.json',
data=params
)
response.raise_for_status()
return response.json()
def modify_nameservers(self, order_id: str,
nameservers: List[str]) -> Dict[str, Any]:
"""Update nameservers for a domain."""
params = list({
**self._get_auth_params(),
'order-id': order_id
}.items())
for ns in nameservers:
params.append(('ns', ns))
response = requests.post(
f'{self.base_url}/domains/modify-ns.json',
data=params
)
response.raise_for_status()
return response.json()
def renew_domain(self, order_id: str, years: int,
exp_date: int) -> Dict[str, Any]:
"""Renew a domain registration."""
params = {
**self._get_auth_params(),
'order-id': order_id,
'years': str(years),
'exp-date': str(exp_date), # Unix timestamp
'invoice-option': 'KeepInvoice'
}
response = requests.post(
f'{self.base_url}/domains/renew.json',
data=params
)
response.raise_for_status()
return response.json()
# Usage example
rc = ResellerClubAPI('your-reseller-id', 'your-api-key', test_mode=False)
# Check availability
results = rc.check_availability(['mybusiness'], ['com', 'net', 'io'])
for domain, status in results.items():
availability = 'Available' if status.get('status') == 'available' else 'Taken'
print(f'{domain}: {availability}')
Common Automation Workflows
1. Domain Expiration Monitoring
import asyncio
from datetime import datetime, timedelta
from typing import List, Dict
class DomainExpirationMonitor:
def __init__(self, registrar_api):
self.api = registrar_api
self.warning_days = [90, 60, 30, 14, 7, 1]
async def check_expirations(self) -> List[Dict]:
"""Check all domains for upcoming expirations."""
domains = self.api.list_domains()
alerts = []
for domain in domains:
expires = datetime.fromisoformat(domain['expires'].replace('Z', '+00:00'))
days_until_expiry = (expires - datetime.now(expires.tzinfo)).days
if days_until_expiry in self.warning_days:
alerts.append({
'domain': domain['name'],
'expires': expires.isoformat(),
'days_remaining': days_until_expiry,
'action_required': days_until_expiry <= 30
})
return alerts
async def auto_renew_expiring(self, max_days: int = 7) -> List[str]:
"""Auto-renew domains expiring within specified days."""
domains = self.api.list_domains()
renewed = []
for domain in domains:
expires = datetime.fromisoformat(domain['expires'].replace('Z', '+00:00'))
days_until_expiry = (expires - datetime.now(expires.tzinfo)).days
if days_until_expiry <= max_days and domain.get('auto_renew'):
try:
self.api.renew_domain(domain['name'], years=1)
renewed.append(domain['name'])
except Exception as e:
print(f"Failed to renew {domain['name']}: {e}")
return renewed
2. DNS Record Synchronization
class DnsSyncManager {
constructor(sourceApi, targetApi) {
this.source = sourceApi;
this.target = targetApi;
}
async syncDnsRecords(domain) {
// Get source records
const sourceRecords = await this.source.getDnsRecords(domain);
// Get target zone
const targetZone = await this.target.getZoneByName(domain);
const targetRecords = await this.target.listDnsRecords(targetZone.id);
// Create lookup map for existing target records
const targetMap = new Map(
targetRecords.map(r => [`${r.type}:${r.name}`, r])
);
for (const record of sourceRecords) {
const key = `${record.type}:${record.name}`;
const existing = targetMap.get(key);
if (existing) {
// Update if different
if (existing.content !== record.data) {
await this.target.updateDnsRecord(
targetZone.id,
existing.id,
{
type: record.type,
name: record.name,
content: record.data,
ttl: record.ttl
}
);
console.log(`Updated ${key}`);
}
} else {
// Create new record
await this.target.createDnsRecord(targetZone.id, {
type: record.type,
name: record.name,
content: record.data,
ttl: record.ttl
});
console.log(`Created ${key}`);
}
}
}
}
3. Bulk Domain Availability Checker
import asyncio
import aiohttp
from typing import List, Dict, Tuple
class BulkAvailabilityChecker:
def __init__(self, api_credentials: Dict[str, str]):
self.credentials = api_credentials
self.rate_limit = 10 # requests per second
self.semaphore = asyncio.Semaphore(self.rate_limit)
async def check_single(self, session: aiohttp.ClientSession,
domain: str) -> Tuple[str, bool]:
"""Check availability of a single domain."""
async with self.semaphore:
# Add delay for rate limiting
await asyncio.sleep(1 / self.rate_limit)
try:
async with session.get(
'https://api.godaddy.com/v1/domains/available',
params={'domain': domain},
headers={
'Authorization': f"sso-key {self.credentials['key']}:{self.credentials['secret']}"
}
) as response:
data = await response.json()
return (domain, data.get('available', False))
except Exception as e:
print(f"Error checking {domain}: {e}")
return (domain, None)
async def check_bulk(self, domains: List[str]) -> Dict[str, bool]:
"""Check availability of multiple domains concurrently."""
async with aiohttp.ClientSession() as session:
tasks = [self.check_single(session, d) for d in domains]
results = await asyncio.gather(*tasks)
return dict(results)
# Usage
async def main():
checker = BulkAvailabilityChecker({
'key': 'your-api-key',
'secret': 'your-api-secret'
})
domains_to_check = [
'techstartup2025.com',
'cloudservices.io',
'myawesomebusiness.net',
# ... more domains
]
results = await checker.check_bulk(domains_to_check)
available = [d for d, avail in results.items() if avail]
print(f"Available domains: {available}")
asyncio.run(main())
Error Handling and Retries
Robust error handling is essential for production API integrations:
import time
import random
from functools import wraps
from typing import Callable, Any
class APIError(Exception):
"""Base exception for API errors."""
def __init__(self, message: str, status_code: int = None,
response: dict = None):
self.message = message
self.status_code = status_code
self.response = response
super().__init__(self.message)
class RateLimitError(APIError):
"""Raised when API rate limit is exceeded."""
pass
class AuthenticationError(APIError):
"""Raised when API authentication fails."""
pass
def retry_with_backoff(max_retries: int = 3,
base_delay: float = 1.0,
max_delay: float = 60.0,
exponential_base: float = 2.0,
jitter: bool = True):
"""Decorator for retrying API calls with exponential backoff."""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except RateLimitError as e:
last_exception = e
# Use retry-after header if available
retry_after = e.response.get('retry-after', base_delay)
delay = min(float(retry_after), max_delay)
except (ConnectionError, TimeoutError) as e:
last_exception = e
delay = min(
base_delay * (exponential_base ** attempt),
max_delay
)
except AuthenticationError:
raise # Don't retry auth errors
except Exception as e:
last_exception = e
delay = base_delay
if attempt < max_retries:
if jitter:
delay *= (0.5 + random.random())
print(f"Retry {attempt + 1}/{max_retries} in {delay:.2f}s")
time.sleep(delay)
raise last_exception
return wrapper
return decorator
class RobustAPIClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.api_key = api_key
@retry_with_backoff(max_retries=3, base_delay=1.0)
def make_request(self, endpoint: str, method: str = 'GET',
data: dict = None) -> dict:
"""Make an API request with automatic retry."""
import requests
url = f"{self.base_url}/{endpoint}"
headers = {'Authorization': f'Bearer {self.api_key}'}
response = requests.request(
method, url, headers=headers, json=data, timeout=30
)
if response.status_code == 429:
raise RateLimitError(
"Rate limit exceeded",
status_code=429,
response={'retry-after': response.headers.get('Retry-After', 60)}
)
elif response.status_code == 401:
raise AuthenticationError(
"Authentication failed",
status_code=401
)
elif response.status_code >= 400:
raise APIError(
f"API error: {response.text}",
status_code=response.status_code,
response=response.json() if response.text else {}
)
return response.json()
Rate Limiting Strategies
Implement proper rate limiting to avoid API blocks:
class RateLimiter {
constructor(requestsPerMinute = 60) {
this.requestsPerMinute = requestsPerMinute;
this.tokens = requestsPerMinute;
this.lastRefill = Date.now();
this.queue = [];
this.processing = false;
}
refillTokens() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000 / 60; // minutes
const tokensToAdd = elapsed * this.requestsPerMinute;
this.tokens = Math.min(
this.requestsPerMinute,
this.tokens + tokensToAdd
);
this.lastRefill = now;
}
async acquire() {
return new Promise((resolve) => {
this.queue.push(resolve);
this.processQueue();
});
}
async processQueue() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
this.refillTokens();
if (this.tokens >= 1) {
this.tokens -= 1;
const resolve = this.queue.shift();
resolve();
} else {
// Calculate wait time for next token
const waitTime = (1 / this.requestsPerMinute) * 60 * 1000;
await new Promise(r => setTimeout(r, waitTime));
}
}
this.processing = false;
}
}
// Usage with API client
class RateLimitedAPIClient {
constructor(apiKey, requestsPerMinute = 60) {
this.apiKey = apiKey;
this.rateLimiter = new RateLimiter(requestsPerMinute);
}
async makeRequest(url, options = {}) {
await this.rateLimiter.acquire();
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.apiKey}`
}
});
return response.json();
}
}
Security Best Practices
1. Secure Credential Storage
import os
from cryptography.fernet import Fernet
class SecureCredentialManager:
"""Manage API credentials securely."""
def __init__(self, encryption_key: bytes = None):
self.key = encryption_key or os.environ.get('ENCRYPTION_KEY')
if self.key:
self.cipher = Fernet(self.key)
else:
self.cipher = None
def get_credential(self, name: str) -> str:
"""Get credential from environment or encrypted storage."""
# First, try environment variable
env_value = os.environ.get(name)
if env_value:
return env_value
# Try encrypted file
encrypted_file = f".credentials/{name}.enc"
if os.path.exists(encrypted_file) and self.cipher:
with open(encrypted_file, 'rb') as f:
encrypted = f.read()
return self.cipher.decrypt(encrypted).decode()
raise ValueError(f"Credential {name} not found")
def store_credential(self, name: str, value: str) -> None:
"""Store credential in encrypted format."""
if not self.cipher:
raise ValueError("Encryption key required to store credentials")
os.makedirs('.credentials', exist_ok=True)
encrypted = self.cipher.encrypt(value.encode())
with open(f".credentials/{name}.enc", 'wb') as f:
f.write(encrypted)
# Usage in production
credentials = SecureCredentialManager()
api_key = credentials.get_credential('GODADDY_API_KEY')
api_secret = credentials.get_credential('GODADDY_API_SECRET')
2. Request Signing (for APIs that support it)
const crypto = require('crypto');
function signRequest(method, path, timestamp, apiSecret, body = '') {
const message = `${method}\n${path}\n${timestamp}\n${body}`;
return crypto
.createHmac('sha256', apiSecret)
.update(message)
.digest('hex');
}
// Usage
const timestamp = Math.floor(Date.now() / 1000);
const signature = signRequest(
'POST',
'/v1/domains/register',
timestamp,
apiSecret,
JSON.stringify(requestBody)
);
3. API Key Rotation
class APIKeyRotationManager:
"""Manage periodic API key rotation."""
def __init__(self, registrar_api, key_age_days: int = 90):
self.api = registrar_api
self.key_age_days = key_age_days
def check_key_age(self) -> bool:
"""Check if API key needs rotation."""
key_created = self.get_key_creation_date()
age = (datetime.now() - key_created).days
return age >= self.key_age_days
def rotate_key(self) -> str:
"""Generate new API key and invalidate old one."""
# Create new key
new_key = self.api.create_api_key()
# Test new key
test_result = self.api.test_connection(new_key)
if not test_result:
raise Exception("New key validation failed")
# Invalidate old key
self.api.revoke_api_key(self.api.current_key)
# Update stored credentials
self.update_stored_key(new_key)
return new_key
Building a Multi-Registrar Wrapper
Create a unified interface across multiple registrars:
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
from enum import Enum
class RegistrarType(Enum):
GODADDY = "godaddy"
NAMECHEAP = "namecheap"
CLOUDFLARE = "cloudflare"
RESELLERCLUB = "resellerclub"
class DomainInfo:
def __init__(self, name: str, expires: str, locked: bool,
auto_renew: bool, nameservers: List[str]):
self.name = name
self.expires = expires
self.locked = locked
self.auto_renew = auto_renew
self.nameservers = nameservers
class BaseRegistrarAdapter(ABC):
"""Abstract base class for registrar adapters."""
@abstractmethod
def get_domain_info(self, domain: str) -> DomainInfo:
pass
@abstractmethod
def list_domains(self) -> List[DomainInfo]:
pass
@abstractmethod
def check_availability(self, domain: str) -> bool:
pass
@abstractmethod
def update_nameservers(self, domain: str,
nameservers: List[str]) -> bool:
pass
@abstractmethod
def renew_domain(self, domain: str, years: int) -> bool:
pass
class GoDaddyAdapter(BaseRegistrarAdapter):
def __init__(self, api_key: str, api_secret: str):
self.api = GoDaddyAPI(api_key, api_secret, production=True)
def get_domain_info(self, domain: str) -> DomainInfo:
data = self.api.get_domain_info(domain)
return DomainInfo(
name=data['domain'],
expires=data['expires'],
locked=data.get('locked', False),
auto_renew=data.get('renewAuto', False),
nameservers=data.get('nameServers', [])
)
def list_domains(self) -> List[DomainInfo]:
domains = self.api.list_domains()
return [self._convert_domain(d) for d in domains]
def check_availability(self, domain: str) -> bool:
result = self.api.check_availability(domain)
return result.get('available', False)
def update_nameservers(self, domain: str,
nameservers: List[str]) -> bool:
self.api.update_nameservers(domain, nameservers)
return True
def renew_domain(self, domain: str, years: int = 1) -> bool:
self.api.renew_domain(domain, years)
return True
class NamecheapAdapter(BaseRegistrarAdapter):
def __init__(self, api_user: str, api_key: str,
username: str, client_ip: str):
self.api = NamecheapAPI(api_user, api_key, username, client_ip, False)
def get_domain_info(self, domain: str) -> DomainInfo:
data = self.api.get_domain_info(domain)
return DomainInfo(
name=data['domain'],
expires=data['expires'],
locked=data['locked'],
auto_renew=data['auto_renew'],
nameservers=[] # Requires separate API call
)
# ... implement other methods
class UnifiedDomainManager:
"""Unified interface for managing domains across registrars."""
def __init__(self):
self.adapters: Dict[RegistrarType, BaseRegistrarAdapter] = {}
def register_adapter(self, registrar: RegistrarType,
adapter: BaseRegistrarAdapter) -> None:
self.adapters[registrar] = adapter
def get_all_domains(self) -> Dict[RegistrarType, List[DomainInfo]]:
"""Get all domains from all registered registrars."""
results = {}
for registrar, adapter in self.adapters.items():
try:
results[registrar] = adapter.list_domains()
except Exception as e:
print(f"Error fetching from {registrar}: {e}")
results[registrar] = []
return results
def find_domain(self, domain: str) -> Optional[tuple]:
"""Find which registrar manages a specific domain."""
for registrar, adapter in self.adapters.items():
try:
info = adapter.get_domain_info(domain)
return (registrar, info)
except:
continue
return None
def check_availability_all(self, domain: str) -> Dict[str, bool]:
"""Check availability across all registrars."""
results = {}
for registrar, adapter in self.adapters.items():
try:
results[registrar.value] = adapter.check_availability(domain)
except Exception as e:
results[registrar.value] = None
return results
# Usage
manager = UnifiedDomainManager()
manager.register_adapter(
RegistrarType.GODADDY,
GoDaddyAdapter('key', 'secret')
)
manager.register_adapter(
RegistrarType.NAMECHEAP,
NamecheapAdapter('user', 'key', 'username', '1.2.3.4')
)
# Get all domains
all_domains = manager.get_all_domains()
# Check availability across registrars
availability = manager.check_availability_all('mynewstartup.com')
Testing and Sandbox Environments
Using Test/Sandbox Environments
| Registrar | Sandbox URL | Notes |
|---|---|---|
| GoDaddy | api.ote-godaddy.com | Requires separate OTE API key |
| Namecheap | api.sandbox.namecheap.com | Full functionality available |
| ResellerClub | test.httpapi.com | Complete test environment |
| Cloudflare | No sandbox | Use test zones in production |
Integration Testing Example
import pytest
from unittest.mock import Mock, patch
class TestDomainAPIIntegration:
"""Integration tests for domain API wrappers."""
@pytest.fixture
def mock_godaddy_api(self):
"""Create mock GoDaddy API for testing."""
with patch('requests.get') as mock_get:
mock_get.return_value.json.return_value = {
'available': True,
'domain': 'testdomain.com',
'price': 1199
}
mock_get.return_value.status_code = 200
yield mock_get
def test_check_availability(self, mock_godaddy_api):
"""Test domain availability check."""
api = GoDaddyAPI('test-key', 'test-secret', production=False)
result = api.check_availability('testdomain.com')
assert result['available'] == True
assert result['domain'] == 'testdomain.com'
def test_rate_limiting(self):
"""Test rate limiter behavior."""
limiter = RateLimiter(requestsPerMinute=60)
# Should allow immediate request
start = time.time()
await limiter.acquire()
first_request_time = time.time() - start
assert first_request_time < 0.1
@pytest.mark.integration
def test_sandbox_domain_operations(self):
"""Test full domain operation cycle in sandbox."""
api = GoDaddyAPI(
os.environ['GODADDY_OTE_KEY'],
os.environ['GODADDY_OTE_SECRET'],
production=False # Use OTE environment
)
# Test availability
result = api.check_availability('test-integration-12345.com')
assert 'available' in result
# Test domain listing
domains = api.list_domains()
assert isinstance(domains, list)
Webhooks and Event Notifications
Some registrars support webhooks for event notifications:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Webhook signature verification
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Webhook endpoint
app.post('/webhooks/domain-events', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = req.body;
switch (event.type) {
case 'domain.expiring':
handleExpiringDomain(event.data);
break;
case 'domain.transferred':
handleDomainTransfer(event.data);
break;
case 'dns.changed':
handleDnsChange(event.data);
break;
case 'domain.renewed':
handleDomainRenewal(event.data);
break;
default:
console.log(`Unknown event type: ${event.type}`);
}
res.json({ received: true });
});
async function handleExpiringDomain(data) {
console.log(`Domain ${data.domain} expires in ${data.daysRemaining} days`);
// Send notification
await sendSlackAlert({
text: `Domain Expiration Warning`,
attachments: [{
color: data.daysRemaining <= 7 ? 'danger' : 'warning',
fields: [
{ title: 'Domain', value: data.domain },
{ title: 'Expires', value: data.expirationDate },
{ title: 'Days Remaining', value: data.daysRemaining }
]
}]
});
}
app.listen(3000);
Frequently Asked Questions
Which registrar API is easiest to start with?
Cloudflare offers the most accessible API for DNS management - free access, excellent documentation, and no minimum account requirements. However, for full domain registration capabilities, Namecheap has lower barriers ($50 balance or 20 domains) compared to GoDaddy's 50-domain requirement.
Can I register domains programmatically through all these APIs?
GoDaddy, Namecheap, and ResellerClub all support domain registration via API. Cloudflare's registration endpoints require an Enterprise plan - most users manage transfers and DNS through their API instead.
How do I handle rate limits across multiple registrar APIs?
Implement a unified rate limiter that tracks requests per registrar. GoDaddy allows 60 requests/minute, Cloudflare allows 1,200/5 minutes, and Namecheap has more generous limits. Use exponential backoff when you receive 429 errors.
Are sandbox/test environments free to use?
Yes. GoDaddy's OTE environment, Namecheap's sandbox, and ResellerClub's test mode are all free. However, you need separate API credentials for test environments. Cloudflare doesn't offer a sandbox - test against development zones in production.
How do I securely store API credentials in production?
Use environment variables, encrypted credential files, or secrets management services (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault). Never commit API keys to source control. Rotate keys every 90 days and use the principle of least privilege when generating API tokens.
Can I automate domain transfers between registrars?
Yes, but transfers require authorization codes (EPP codes) that typically need manual retrieval from the losing registrar's dashboard. Some APIs expose EPP code retrieval, but ICANN's 60-day transfer lock after registration/transfer means full automation has limitations.
What happens if an API request fails during a critical operation?
Implement idempotent operations where possible. For registration and renewal, most APIs return order IDs you can query to check status. Use transaction-style logging so you can manually recover if automation fails mid-operation.
Do these APIs support bulk operations?
Namecheap and ResellerClub support checking multiple domains in a single API call. GoDaddy's bulk operations are more limited. For large portfolios, implement parallel requests with proper rate limiting rather than relying on bulk endpoints.
Key Takeaways
- Access requirements vary significantly: GoDaddy requires 50+ domains, Namecheap needs $50+ balance or 20 domains, Cloudflare is free for DNS but Enterprise-only for registration
- Rate limits matter: Plan for 60-100 requests per minute and implement exponential backoff for 429 errors
- Use sandbox environments: Test thoroughly in OTE/sandbox before production deployment
- Authentication methods differ: GoDaddy uses sso-key headers, Namecheap uses XML with query parameters, Cloudflare uses Bearer tokens
- Build registrar-agnostic abstractions: A unified adapter pattern lets you switch registrars without rewriting business logic
- Secure credentials properly: Use environment variables, encrypted storage, and regular key rotation
- Plan for failures: Implement retry logic, logging, and manual recovery procedures for critical operations
Next Steps
Immediate Actions
- Audit your requirements - Determine if you meet access thresholds for each registrar API
- Set up sandbox testing - Create OTE/sandbox credentials before touching production
- Implement rate limiting - Build this into your client before making bulk requests
- Create monitoring - Track API usage, errors, and domain expirations
Related Reading
- Querying RDAP Programmatically - Build domain lookup tools using the RDAP protocol
- Understanding EPP Protocol - Learn the protocol underlying registrar communications
- DNS Record Types Explained - Master DNS management for API automation
- Domain Lifecycle Stages - Understand registration, renewal, and deletion timelines
DomainDetails Pro Features
Use DomainDetails.com to complement your API automation:
- Domain Monitoring - Track changes to domains you're interested in
- Bulk Lookup - Research multiple domains before API operations
- WHOIS History - Investigate domain ownership patterns
Research Sources
- GoDaddy Developer Portal - Official API documentation and getting started guides
- GoDaddy Domains API Documentation - Endpoint reference
- Namecheap API Introduction - API setup and requirements
- Namecheap API Methods - Complete method reference
- Cloudflare Registrar Documentation - Registrar features and limitations
- Cloudflare API Reference - DNS and domain endpoints
- ResellerClub API Guide - Reseller HTTP API documentation
- ICANN Registration Data Policy - Data handling requirements effective August 2025