Summary
Manual domain transfers break at scale because every human step — unlocking the domain, retrieving an auth code, initiating the transfer, clicking through confirmation emails — has no retry logic, no audit trail, and no way to detect failure until it's already happened. Every one of those steps maps directly to a name.com API call, making the workflow idempotent, auditable, and repeatable. This tutorial walks through a complete implementation in Python and curl: HTTP Basic Auth setup, auth code retrieval, transfer initiation, status polling with exponential backoff, cancellation, and a bulk migration script with CSV logging.
Moving domains between registrars is one of those tasks that feels simple until you're doing it at scale. The math is straightforward: 10 minutes per domain × 50 domains = roughly 8 hours of manual work, and that assumes nothing goes wrong. Stale auth codes, missed unlock steps, forgotten confirmation emails, and zero visibility into in-flight transfer status mean something frequently goes wrong.
Every step of the transfer lifecycle maps directly to an API call. Scripting the workflow makes it idempotent, auditable, and repeatable. This tutorial walks through a complete implementation using the name.com API, from HTTP Basic Auth setup through bulk migration with status polling and error handling.
Why Manual Domain Transfers Break at Scale
A typical manual transfer cycle: log into the losing registrar's UI, disable WHOIS privacy, unlock the domain, generate an auth code, copy it somewhere safe, initiate the transfer at the gaining registrar, wait for a confirmation email, click through an approval link, then check back daily until the transfer completes or times out. Each domain takes 8-12 minutes when everything works.
At 50 domains, you're looking at 8+ hours spread across multiple sessions, with state tracked in a spreadsheet that has no retry logic, no idempotency, and no audit trail. The fix isn't faster clicking — every one of those steps is available through a registrar API.
The Domain Transfer Lifecycle
Before initiating any transfer, validate four preconditions: the domain is unlocked (clientTransferProhibited flag cleared), WHOIS privacy is disabled on TLDs that require it, the domain is more than 60 days old since registration or last transfer (ICANN policy), and zone records are backed up since DNS configuration doesn't travel with the domain.
The transfer state machine: initiated → pendingTransfer → pendingApproval (some registrars) → complete. It can also go to cancelled (within 5 days) or failed (invalid auth code or locked domain).
Retrieving the Domain Auth Code
Auth codes are time-sensitive. Retrieve them immediately before calling the transfer initiation endpoint, not as a pre-batch step hours earlier. For most TLDs, codes are valid for up to 7 days.
curl -u "yourusername:your_api_token" \
https://api.name.com/core/v1/domains/example.com:getAuthCodedef get_auth_code(session, domain):
resp = session.get(f"{BASE_URL}/domains/{domain}:getAuthCode")
resp.raise_for_status()
return resp.json()["authCode"]Initiating the Domain Transfer
curl -u "yourusername:your_api_token" --request POST \
--url https://api.name.com/core/v1/transfers \
--header 'Content-Type: application/json' \
--data '{
"authCode": "Xk9#mP2qL8wR",
"domainName": "example.com",
"privacyEnabled": true
}'def initiate_transfer(session, domain, auth_code):
payload = {
"domainName": domain,
"authCode": auth_code
}
resp = session.post(f"{BASE_URL}/transfers", json=payload)
if resp.status_code == 200:
return resp.json()
raise RuntimeError(f"Transfer failed for {domain}: {resp.status_code} {resp.text}")Polling Transfer Status with Exponential Backoff
The total transfer window is up to 7 days. Your polling loop needs to be patient. Use exponential backoff starting at 5-minute intervals, doubling each pass, capped at 60 minutes:
import time
def poll_transfer(session, domain, max_hours=168): # 7 days
interval = 300 # start at 5 minutes
max_interval = 3600 # cap at 60 minutes
elapsed = 0
while elapsed < max_hours * 3600:
resp = session.get(f"{BASE_URL}/transfers/{domain}")
data = resp.json()
status = data.get("status")
if status == "complete":
print(f"{domain}: transfer complete")
return status
elif status in ("cancelled", "failed"):
print(f"{domain}: terminal state {status} - {data}")
return status
elif status == "pendingApproval":
print(f"{domain}: pending approval - check registrar dashboard")
time.sleep(interval)
elapsed += interval
interval = min(interval * 2, max_interval)
raise TimeoutError(f"Transfer polling timed out for {domain}")Scripting a Bulk Domain Migration
The full bulk migration script reads from a CSV with two columns: domain and auth_code. Leave auth_code blank for domains already registered at name.com — the script retrieves it programmatically. The idempotency check at the top of the loop prevents duplicate transfers on re-runs. If the script fails at domain 23 of 50, re-running it skips the first 22 already logged in the output CSV.
import csv
import time
import requests
from datetime import datetime
session = requests.Session()
session.auth = ("yourusername", "your_api_token")
BASE_URL = "https://api.name.com/core/v1"
def run_bulk_transfer(input_csv, output_csv):
existing = load_existing_transfers(output_csv)
domains = list(csv.DictReader(open(input_csv)))
for row in domains:
domain = row["domain"]
auth_code = row.get("auth_code", "").strip()
# Idempotency: skip if already initiated
if domain in existing:
print(f"{domain}: already initiated, transfer ID {existing[domain]}")
continue
# Retrieve auth code if not in CSV
if not auth_code:
try:
auth_code = get_auth_code(session, domain)
except Exception as e:
print(f"{domain}: auth code retrieval failed - {e}")
log_result(output_csv, domain, "", "auth_code_failed")
continue
# Initiate transfer
try:
data = initiate_transfer(session, domain, auth_code)
log_result(output_csv, domain, domain, data.get("status"))
print(f"{domain}: initiated, status={data.get('status')}")
except Exception as e:
print(f"{domain}: initiation failed - {e}")
log_result(output_csv, domain, "", "initiation_failed")
continue
time.sleep(2) # throttle between requestsSequential processing with a 2-second sleep is conservative by design. The name.com API enforces a rate limit of 20 requests per second and 3,000 requests per hour, so sequential processing with a short delay keeps you well within both ceilings for batch operations.
How do I authenticate with the name.com API to run domain transfers?
The name.com API uses HTTP Basic Auth over HTTPS. Generate an API token at name.com/account/settings/api — no approval queue, takes under 2 minutes. Pass your username and token as username:api_token, Base64-encoded in the Authorization header. With curl, use the -u flag; in Python, set up a requests.Session with .auth once and reuse it across all calls.
How do I retrieve a domain auth code programmatically?
Call GET /domains/{domainName}:getAuthCode. The response returns a single authCode field. Retrieve it immediately before initiating the transfer — not as a pre-batch step — because codes expire within 7 days for most TLDs. If you pre-fetch codes for a large batch and hit errors midway through, early codes may expire before you use them.
What error codes should I handle when initiating a domain transfer?
Three errors deserve explicit handling: 409 Conflict means the domain is already in an active transfer; 422 Unprocessable Entity means the TLD isn't supported for transfer-in at this time; 400 Bad Request means a malformed payload, usually a missing required field. Log the full error body on failure — the response text carries the specific reason.
How do I poll transfer status without keeping a process alive for days?
Use GET /transfers/{domainName} with exponential backoff — start at 5-minute intervals, double each pass, cap at 60 minutes. The transfer window is up to 7 days, so a long-lived polling loop is impractical for bulk jobs. The better production pattern is to run the polling loop as a separate script pass against your audit CSV, or subscribe to name.com webhooks that fire on state changes so you can react immediately.
Can I cancel a domain transfer via API, and when?
Yes — call POST /transfers/{domainName}:cancel. Cancellation is only available within the first 5 days of a transfer. The most common reason to cancel is a stale or incorrect auth code: cancel, retrieve a fresh code, and re-initiate. Once a transfer reaches complete, cancellation is no longer possible.
