Files
namecheap-api/namecheap.py
kaffa 896699535d Initial commit: Namecheap API library with REST/MCP servers
Features:
- Domain management (check, register, renew, contacts)
- DNS management (nameservers, records)
- Glue records (child nameserver) support
- TLD price tracking with KRW conversion
- FastAPI REST server with OpenAI schema
- MCP server for Claude integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 10:21:46 +09:00

550 lines
19 KiB
Python

"""
Namecheap API Python Wrapper
"""
import requests
import xml.etree.ElementTree as ET
from dataclasses import dataclass
NS = {"nc": "http://api.namecheap.com/xml.response"}
@dataclass
class NamecheapConfig:
api_user: str
api_key: str
username: str
client_ip: str
sandbox: bool = False
@property
def base_url(self) -> str:
if self.sandbox:
return "https://api.sandbox.namecheap.com/xml.response"
return "https://api.namecheap.com/xml.response"
@dataclass
class RegistrantInfo:
first_name: str
last_name: str
address1: str
city: str
state_province: str
postal_code: str
country: str
phone: str
email: str
organization: str = ""
address2: str = ""
class NamecheapError(Exception):
"""Namecheap API error"""
def __init__(self, code: str, message: str):
self.code = code
self.message = message
super().__init__(f"[{code}] {message}")
class NamecheapAPI:
def __init__(self, config: NamecheapConfig):
self.config = config
def _base_params(self) -> dict:
return {
"ApiUser": self.config.api_user,
"ApiKey": self.config.api_key,
"UserName": self.config.username,
"ClientIp": self.config.client_ip,
}
def _request(self, command: str, **kwargs) -> ET.Element:
params = self._base_params()
params["Command"] = command
params.update(kwargs)
response = requests.get(self.config.base_url, params=params, timeout=30)
response.raise_for_status()
root = ET.fromstring(response.text)
status = root.attrib.get("Status")
if status == "ERROR":
errors = root.find("nc:Errors", NS)
if errors is not None:
error = errors.find("nc:Error", NS)
if error is not None:
raise NamecheapError(
error.attrib.get("Number", "Unknown"),
error.text or "Unknown error"
)
raise NamecheapError("Unknown", "API returned error status")
return root
# ===== Domains =====
def domains_check(self, domains: list[str]) -> dict[str, bool]:
"""Check domain availability"""
domain_list = ",".join(domains)
root = self._request("namecheap.domains.check", DomainList=domain_list)
result = {}
for domain in root.findall(".//nc:DomainCheckResult", NS):
name = domain.attrib.get("Domain", "")
available = domain.attrib.get("Available", "false").lower() == "true"
result[name] = available
return result
def domains_get_list(self, page: int = 1, page_size: int = 20) -> list[dict]:
"""Get list of domains in account"""
root = self._request(
"namecheap.domains.getList",
Page=str(page),
PageSize=str(page_size)
)
domains = []
for domain in root.findall(".//nc:Domain", NS):
domains.append({
"id": domain.attrib.get("ID"),
"name": domain.attrib.get("Name"),
"user": domain.attrib.get("User"),
"created": domain.attrib.get("Created"),
"expires": domain.attrib.get("Expires"),
"is_expired": domain.attrib.get("IsExpired") == "true",
"is_locked": domain.attrib.get("IsLocked") == "true",
"auto_renew": domain.attrib.get("AutoRenew") == "true",
"whois_guard": domain.attrib.get("WhoisGuard"),
})
return domains
def domains_get_info(self, domain: str) -> dict:
"""Get detailed info about a domain"""
root = self._request("namecheap.domains.getInfo", DomainName=domain)
info_elem = root.find(".//nc:DomainGetInfoResult", NS)
if info_elem is None:
return {}
return {
"domain": info_elem.attrib.get("DomainName"),
"owner": info_elem.attrib.get("OwnerName"),
"status": info_elem.attrib.get("Status"),
"is_premium": info_elem.attrib.get("IsPremiumName") == "true",
}
def domains_create(
self,
domain: str,
registrant: RegistrantInfo,
years: int = 1,
add_whois_guard: bool = True,
) -> dict:
"""
Register a new domain.
Args:
domain: Domain name to register (e.g., "example.com")
registrant: Registrant contact information
years: Number of years to register (1-10)
add_whois_guard: Enable WhoisGuard privacy protection
Returns:
dict with domain, registered, charged_amount, etc.
"""
params = {
"DomainName": domain,
"Years": str(years),
"AddFreeWhoisguard": "yes" if add_whois_guard else "no",
"WGEnabled": "yes" if add_whois_guard else "no",
}
# Contact info for all 4 contact types (Registrant, Tech, Admin, AuxBilling)
for prefix in ["Registrant", "Tech", "Admin", "AuxBilling"]:
params[f"{prefix}FirstName"] = registrant.first_name
params[f"{prefix}LastName"] = registrant.last_name
params[f"{prefix}Address1"] = registrant.address1
params[f"{prefix}City"] = registrant.city
params[f"{prefix}StateProvince"] = registrant.state_province
params[f"{prefix}PostalCode"] = registrant.postal_code
params[f"{prefix}Country"] = registrant.country
params[f"{prefix}Phone"] = registrant.phone
params[f"{prefix}EmailAddress"] = registrant.email
if registrant.organization:
params[f"{prefix}OrganizationName"] = registrant.organization
if registrant.address2:
params[f"{prefix}Address2"] = registrant.address2
root = self._request("namecheap.domains.create", **params)
result = root.find(".//nc:DomainCreateResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("Domain"),
"registered": result.attrib.get("Registered") == "true",
"charged_amount": float(result.attrib.get("ChargedAmount", 0)),
"domain_id": result.attrib.get("DomainID"),
"order_id": result.attrib.get("OrderID"),
"transaction_id": result.attrib.get("TransactionID"),
"whois_guard_enabled": result.attrib.get("WhoisguardEnable") == "true",
}
def domains_renew(self, domain: str, years: int = 1) -> dict:
"""
Renew a domain.
Args:
domain: Domain name to renew (e.g., "example.com")
years: Number of years to renew (1-10)
Returns:
dict with domain, renewed, charged_amount, expiration_date, etc.
"""
root = self._request(
"namecheap.domains.renew",
DomainName=domain,
Years=str(years),
)
result = root.find(".//nc:DomainRenewResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("DomainName"),
"domain_id": result.attrib.get("DomainID"),
"renewed": result.attrib.get("Renew") == "true",
"charged_amount": float(result.attrib.get("ChargedAmount", 0)),
"order_id": result.attrib.get("OrderID"),
"transaction_id": result.attrib.get("TransactionID"),
"expiration_date": result.attrib.get("DomainDetails", {}).get("ExpiredDate") if isinstance(result.attrib.get("DomainDetails"), dict) else None,
}
def _parse_contact(self, contact_elem) -> dict:
"""Parse contact element to dict"""
if contact_elem is None:
return {}
fields = [
"OrganizationName", "FirstName", "LastName", "Address1", "Address2",
"City", "StateProvince", "PostalCode", "Country", "Phone", "EmailAddress"
]
result = {}
for field in fields:
elem = contact_elem.find(f"nc:{field}", NS)
key = field[0].lower() + field[1:] # camelCase to snake_case style
result[key] = elem.text if elem is not None and elem.text else ""
return result
def domains_get_contacts(self, domain: str) -> dict:
"""
Get domain contact information.
Args:
domain: Domain name (e.g., "example.com")
Returns:
dict with registrant, tech, admin, aux_billing contacts
"""
root = self._request("namecheap.domains.getContacts", DomainName=domain)
result = root.find(".//nc:DomainContactsResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("Domain"),
"registrant": self._parse_contact(result.find("nc:Registrant", NS)),
"tech": self._parse_contact(result.find("nc:Tech", NS)),
"admin": self._parse_contact(result.find("nc:Admin", NS)),
"aux_billing": self._parse_contact(result.find("nc:AuxBilling", NS)),
}
def domains_set_contacts(self, domain: str, registrant: RegistrantInfo) -> bool:
"""
Set domain contact information.
Args:
domain: Domain name (e.g., "example.com")
registrant: Contact information (applied to all contact types)
Returns:
bool indicating success
"""
params = {"DomainName": domain}
for prefix in ["Registrant", "Tech", "Admin", "AuxBilling"]:
params[f"{prefix}FirstName"] = registrant.first_name
params[f"{prefix}LastName"] = registrant.last_name
params[f"{prefix}Address1"] = registrant.address1
params[f"{prefix}City"] = registrant.city
params[f"{prefix}StateProvince"] = registrant.state_province
params[f"{prefix}PostalCode"] = registrant.postal_code
params[f"{prefix}Country"] = registrant.country
params[f"{prefix}Phone"] = registrant.phone
params[f"{prefix}EmailAddress"] = registrant.email
if registrant.organization:
params[f"{prefix}OrganizationName"] = registrant.organization
if registrant.address2:
params[f"{prefix}Address2"] = registrant.address2
root = self._request("namecheap.domains.setContacts", **params)
result = root.find(".//nc:DomainSetContactResult", NS)
return result is not None and result.attrib.get("IsSuccess") == "true"
# ===== DNS =====
def dns_get_list(self, sld: str, tld: str) -> dict:
"""Get nameserver information for a domain"""
root = self._request("namecheap.domains.dns.getList", SLD=sld, TLD=tld)
result = root.find(".//nc:DomainDNSGetListResult", NS)
if result is None:
return {}
nameservers = []
for ns in result.findall("nc:Nameserver", NS):
nameservers.append(ns.text)
return {
"domain": result.attrib.get("Domain"),
"is_using_our_dns": result.attrib.get("IsUsingOurDNS") == "true",
"nameservers": nameservers,
}
def dns_get_hosts(self, sld: str, tld: str) -> list[dict]:
"""Get DNS host records"""
root = self._request("namecheap.domains.dns.getHosts", SLD=sld, TLD=tld)
records = []
for host in root.findall(".//nc:host", NS):
records.append({
"host_id": host.attrib.get("HostId"),
"name": host.attrib.get("Name"),
"type": host.attrib.get("Type"),
"address": host.attrib.get("Address"),
"mx_pref": host.attrib.get("MXPref"),
"ttl": host.attrib.get("TTL"),
})
return records
def dns_set_hosts(self, sld: str, tld: str, records: list[dict]) -> bool:
"""
Set DNS host records.
Each record should have: name, type, address, ttl (optional), mx_pref (optional)
"""
params = {"SLD": sld, "TLD": tld}
for i, record in enumerate(records, 1):
params[f"HostName{i}"] = record["name"]
params[f"RecordType{i}"] = record["type"]
params[f"Address{i}"] = record["address"]
params[f"TTL{i}"] = record.get("ttl", "1800")
if record["type"] == "MX":
params[f"MXPref{i}"] = record.get("mx_pref", "10")
root = self._request("namecheap.domains.dns.setHosts", **params)
result = root.find(".//nc:DomainDNSSetHostsResult", NS)
return result is not None and result.attrib.get("IsSuccess") == "true"
def dns_set_custom(self, sld: str, tld: str, nameservers: list[str]) -> bool:
"""Set custom nameservers"""
ns_string = ",".join(nameservers)
root = self._request(
"namecheap.domains.dns.setCustom",
SLD=sld,
TLD=tld,
Nameservers=ns_string
)
result = root.find(".//nc:DomainDNSSetCustomResult", NS)
return result is not None and result.attrib.get("Updated") == "true"
def dns_set_default(self, sld: str, tld: str) -> bool:
"""Set default Namecheap nameservers"""
root = self._request("namecheap.domains.dns.setDefault", SLD=sld, TLD=tld)
result = root.find(".//nc:DomainDNSSetDefaultResult", NS)
return result is not None and result.attrib.get("Updated") == "true"
# ===== Nameservers (Glue Records) =====
def ns_create(self, sld: str, tld: str, nameserver: str, ip: str) -> dict:
"""
Create a child nameserver (glue record).
Args:
sld: Second-level domain (e.g., "example" for example.com)
tld: Top-level domain (e.g., "com")
nameserver: Nameserver hostname (e.g., "ns1.example.com")
ip: IP address for the nameserver
Returns:
dict with domain, nameserver, ip, success
"""
root = self._request(
"namecheap.domains.ns.create",
SLD=sld,
TLD=tld,
Nameserver=nameserver,
IP=ip,
)
result = root.find(".//nc:DomainNSCreateResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("Domain"),
"nameserver": result.attrib.get("Nameserver"),
"ip": result.attrib.get("IP"),
"success": result.attrib.get("IsSuccess") == "true",
}
def ns_delete(self, sld: str, tld: str, nameserver: str) -> dict:
"""
Delete a child nameserver (glue record).
Args:
sld: Second-level domain (e.g., "example" for example.com)
tld: Top-level domain (e.g., "com")
nameserver: Nameserver hostname to delete (e.g., "ns1.example.com")
Returns:
dict with domain, nameserver, success
"""
root = self._request(
"namecheap.domains.ns.delete",
SLD=sld,
TLD=tld,
Nameserver=nameserver,
)
result = root.find(".//nc:DomainNSDeleteResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("Domain"),
"nameserver": result.attrib.get("Nameserver"),
"success": result.attrib.get("IsSuccess") == "true",
}
def ns_get_info(self, sld: str, tld: str, nameserver: str) -> dict:
"""
Get info about a child nameserver (glue record).
Args:
sld: Second-level domain (e.g., "example" for example.com)
tld: Top-level domain (e.g., "com")
nameserver: Nameserver hostname (e.g., "ns1.example.com")
Returns:
dict with domain, nameserver, ip, statuses
"""
root = self._request(
"namecheap.domains.ns.getInfo",
SLD=sld,
TLD=tld,
Nameserver=nameserver,
)
result = root.find(".//nc:DomainNSInfoResult", NS)
if result is None:
return {}
statuses = []
for status in result.findall("nc:NameserverStatuses/nc:Status", NS):
if status.text:
statuses.append(status.text)
return {
"domain": result.attrib.get("Domain"),
"nameserver": result.attrib.get("Nameserver"),
"ip": result.attrib.get("IP"),
"statuses": statuses,
}
def ns_update(self, sld: str, tld: str, nameserver: str, old_ip: str, ip: str) -> dict:
"""
Update a child nameserver (glue record) IP address.
Args:
sld: Second-level domain (e.g., "example" for example.com)
tld: Top-level domain (e.g., "com")
nameserver: Nameserver hostname (e.g., "ns1.example.com")
old_ip: Current IP address
ip: New IP address
Returns:
dict with domain, nameserver, ip, success
"""
root = self._request(
"namecheap.domains.ns.update",
SLD=sld,
TLD=tld,
Nameserver=nameserver,
OldIP=old_ip,
IP=ip,
)
result = root.find(".//nc:DomainNSUpdateResult", NS)
if result is None:
return {}
return {
"domain": result.attrib.get("Domain"),
"nameserver": result.attrib.get("Nameserver"),
"ip": result.attrib.get("IP"),
"success": result.attrib.get("IsSuccess") == "true",
}
# ===== Account =====
def users_get_balances(self) -> dict:
"""Get account balance"""
root = self._request("namecheap.users.getBalances")
balance = root.find(".//nc:UserGetBalancesResult", NS)
if balance is None:
return {}
return {
"currency": balance.attrib.get("Currency"),
"available_balance": float(balance.attrib.get("AvailableBalance", 0)),
"account_balance": float(balance.attrib.get("AccountBalance", 0)),
"earned_amount": float(balance.attrib.get("EarnedAmount", 0)),
}
def users_get_pricing(self, product_type: str, product_category: str = "") -> list[dict]:
"""Get pricing for products"""
params = {"ProductType": product_type}
if product_category:
params["ProductCategory"] = product_category
root = self._request("namecheap.users.getPricing", **params)
products = []
for product in root.findall(".//nc:Product", NS):
for price in product.findall(".//nc:Price", NS):
products.append({
"name": product.attrib.get("Name"),
"duration": price.attrib.get("Duration"),
"duration_type": price.attrib.get("DurationType"),
"price": float(price.attrib.get("Price", 0)),
"currency": price.attrib.get("Currency"),
})
return products