Summary
Domain registration is the last manual step in most deployment pipelines — even when Terraform, GitHub Actions, and DNS updates all run on a trigger, someone still has to open a browser and buy the domain. The name.com API, built on the OpenAPI 3.1 spec with native MCP support, exposes domain availability, registration, and DNS creation as clean REST endpoints that wrap into three stateless Python functions any agent framework can call as tools. This guide shows how to build those functions once and expose them across LangChain (ReAct agent), CrewAI (sequential crew), and OpenAI Responses (function calling).
Your Terraform modules provision infrastructure in seconds, your GitHub Actions workflow deploys on every push, and then someone still has to open a browser, search a domain registrar, add a name to a cart, and hand-configure DNS records. Everything else in the pipeline runs on a trigger. Domain acquisition runs on a human.
This guide closes that delta. You'll build an AI agent that receives a natural-language prompt, checks domain availability via the name.com API, registers the best match, and sets an A record, all without human intervention. The same underlying Python functions work across LangChain, CrewAI, and OpenAI Responses, so you can drop whichever section fits your stack directly into an existing project.
The name.com API is built on the OpenAPI 3.1 spec with native MCP support — the same infrastructure Vercel, Netlify, Railway, Lovable, and Replit use to power domain registration in their platforms. The published spec means AI coding tools like Cursor and GitHub Copilot can generate accurate wrapper code directly from a plain-English description; the MCP server is what makes the agentic workflow in Section 7 possible. There are no API usage fees — you pay per domain registered.
Prerequisites: a name.com API token (sign up for API access), Python 3.10+, and one of the three agent frameworks installed.
How the autonomous domain agent works: full workflow
The agent executes the following sequence from a single natural-language prompt to a live DNS record:
name.com API: endpoints, authentication, and the OpenAPI spec
The name.com API authenticates with HTTP Basic Auth. Your credentials are username:token, Base64-encoded in the Authorization header:
import base64
import requests
username = "your_username"
token = "your_api_token"
credentials = base64.b64encode(f"{username}:{token}".encode()).decode()
headers = {
"Authorization": f"Basic {credentials}",
"Content-Type": "application/json"
}Use api.dev.name.com during development (it requires separate test credentials) and swap to api.name.com for production. The four endpoints this guide uses all sit on the core/v1 path:
POST /core/v1/domains:checkAvailability— checks availability and returns pricing for one or more domain namesPOST /core/v1/domains— registers a domain, requires a full registrant contact objectPOST /core/v1/domains/{domainName}/records— creates a DNS record with type, host, answer, and TTLGET /core/v1/domains/{domainName}/records— retrieves current DNS records for verification
The published OpenAPI 3.1 spec lets you feed it to Cursor or GitHub Copilot and generate accurate wrapper code from a plain-English description of what you need. You can also import it into Postman to explore endpoints before writing agent code. Run a quick smoke test to confirm auth before building anything:
response = requests.post(
"https://api.dev.name.com/core/v1/domains:checkAvailability",
headers=headers,
json={"domainNames": ["myagentapp.com"]}
)
data = response.json()
print(data["results"][0]["purchasable"], data["results"][0]["purchasePrice"])If you get True and a price back, your credentials are working.
Building the shared Python toolset for domain registration
All three agent implementations consume the same three wrapper functions. Keep them stateless and thin, with error handling here so the agent frameworks receive clean exceptions to reason from.
import base64
import logging
import requests
BASE_URL = "https://api.dev.name.com/core/v1"
USERNAME = "your_username"
TOKEN = "your_api_token"
def _headers():
creds = base64.b64encode(f"{USERNAME}:{TOKEN}".encode()).decode()
return {"Authorization": f"Basic {creds}", "Content-Type": "application/json"}
def check_domain_availability(domain: str) -> dict:
"""Check whether a domain name is available for registration and return its purchase price. Call this before attempting registration."""
resp = requests.post(
f"{BASE_URL}/domains:checkAvailability",
headers=_headers(),
json={"domainNames": [domain]}
)
if resp.status_code != 200:
raise ValueError(f"Availability check failed: {resp.text}")
result = resp.json()["results"][0]
logging.info("Availability check: %s", result)
return result
def register_domain(domain: str, years: int = 1) -> dict:
"""Register an available domain. Raises ValueError if the domain is not purchasable."""
availability = check_domain_availability(domain)
if not availability.get("purchasable"):
raise ValueError(f"{domain} is not available for registration")
payload = {
"domain": {
"domainName": domain,
"privacyEnabled": True
},
"years": 1
}
resp = requests.post(
f"{BASE_URL}/domains",
headers=_headers(),
json=payload
)
if resp.status_code not in (200, 201):
raise ValueError(f"Registration failed: {resp.text}")
logging.info("Registered domain: %s", domain)
return resp.json()
def create_dns_record(domain: str, record_type: str, host: str, answer: str, ttl: int = 300) -> dict:
"""Create a DNS record of the specified type for a registered domain. Use record_type='A' for IPv4 addresses."""
resp = requests.post(
f"{BASE_URL}/domains/{domain}/records",
headers=_headers(),
json={"type": record_type, "host": host, "answer": answer, "ttl": ttl}
)
if resp.status_code not in (200, 201):
raise ValueError(f"DNS record creation failed: {resp.text}")
logging.info("Created %s record for %s: %s", record_type, domain, answer)
return resp.json()register_domain always calls check_domain_availability first and raises if the domain is not purchasable, which prevents the agent from attempting to register a name taken between check and purchase. For agent use, store your registrant object (firstName, lastName, email, phone, address1, city, state, zip, country) in a secrets manager and inject it at runtime. The agent should never prompt a user for registrant details during a session.
LangChain domain API integration: ReAct agent with domain registration tools
Decorate each wrapper with @tool from langchain_core.tools. The docstring on each function is what GPT-4o reads to decide when to invoke it, so write it as a behavioral description, not a parameter signature.
LangChain's @tool decorator requires each function to be independently callable. Because check_domain_availability is also called inside register_domain in the shared toolset, give it a separate decorated copy for LangChain use — check_domain_availability_tool.
from langchain_core.tools import tool
@tool
def check_domain_availability_tool(domain: str) -> dict:
"""Check whether a domain name is available for registration and return its purchase price. Call this before attempting registration."""
# same implementation as the shared toolset above
@tool
def register_domain(domain: str, registrant: dict) -> dict:
"""Register an available domain name. Always call check_domain_availability first. Raises if the domain is not purchasable."""
# ...
@tool
def create_dns_record(domain: str, record_type: str, host: str, answer: str, ttl: int = 300) -> dict:
"""Create a DNS record of the specified type for a registered domain. Use type='A' for IPv4 addresses."""
# ...Build the agent with create_agent:
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langgraph.errors import GraphRecursionError
llm = ChatOpenAI(model="gpt-4o", api_key=OPENAI_API_KEY)
tools = [check_domain_availability_tool, register_domain, create_dns_record]
agent = create_agent(llm, tools=tools)
try:
for chunk in agent.stream(
{
"messages": [
{
"role": "user",
"content": "Find any available .com domain for a Python async job queue library, register it, and point it to 192.168.113.42. Wait a few seconds between registering the domain and creating the A record."
}
]
},
stream_mode="values",
config={"recursion_limit": 50}
):
latest = chunk["messages"][-1]
latest.pretty_print()
except GraphRecursionError:
print("Agent could not complete the task within the iteration limit.")With stream_mode="values", you'll see the full loop: the LLM generates candidate names like asyncqueue.com and pyqueue.io, calls check_domain_availability_tool on each until it finds one that's purchasable, calls register_domain with the registrant config, then calls create_dns_record with the target IP.
Set recursion_limit=50. Without a ceiling, LangChain's ReAct agent retries failed tool calls indefinitely when a domain search exhausts candidates — it needs a hard stop. The ValueError raised by register_domain on unavailable domains gives the agent a structured error to reason from, which prevents silent retries on the same call.
CrewAI autonomous DNS configuration: sequential domain registration crew
CrewAI's @tool decorator accepts the same function signatures as LangChain's, so you import the same wrapper functions and re-decorate them:
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
@tool("check_domain_availability_tool")
def check_domain_availability(domain: str) -> dict:
"""Check whether a domain name is available and return its price. Call before registering."""
# ...
@tool("register_domain")
def register_domain(domain: str) -> dict:
"""Register the specified domain. Raises if unavailable."""
# ...
@tool("create_dns_record")
def create_dns_record(domain: str, record_type: str, host: str, answer: str, ttl: int = 300) -> dict:
"""Create a DNS record for a registered domain."""
# ...The agent and three sequential tasks follow:
domain_engineer = Agent(
role="Domain Registration Specialist",
goal="Search domain availability, register the first available candidate, and configure DNS records.",
backstory="You automate domain acquisition and DNS configuration for deployment pipelines.",
tools=[check_domain_availability_tool, register_domain, create_dns_record],
verbose=True
)
task1 = Task(
description="Check availability for the following candidates: {candidates}. Return the first available domain name and its price.",
expected_output="The name and price of the first available domain.",
agent=domain_engineer
)
task2 = Task(
description="Register the domain returned by the previous task. Use the registrant config from the environment.",
expected_output="Confirmation that the domain has been registered.",
agent=domain_engineer,
human_input=True # confirmation gate before billing
)
task3 = Task(
description="Create an A record pointing the registered domain to {ip}. TTL 300.",
expected_output="Confirmation that the A record is active.",
agent=domain_engineer
)
crew = Crew(
agents=[domain_engineer],
tasks=[task1, task2, task3],
process=Process.sequential,
verbose=True
)
# Execute the crew
crew.kickoff(inputs={
"candidates": ["asyncqueue.io", "asyncqueue.net", "asyncqueue.com"],
"ip": "192.168.113.42"
})human_input=True on Task 2 pauses execution and requests confirmation before the registration call fires — the right default for any user-facing workflow where a billable action requires explicit opt-in.
OpenAI Responses API domain management: function calling implementation
Define a JSON function schema for each wrapper. The description fields are what the model uses to decide which function to call, so write them with the same precision you'd put in a @tool docstring:
pythontools = [
{
"type": "function",
"name": "check_domain_availability_tool",
"description": "Checks whether a domain name is available for registration.",
"parameters": {
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "The full domain name to check, e.g. 'example.com'"
}
},
"required": ["domain"],
"additionalProperties": False
}
},
{
"type": "function",
"name": "register_domain",
"description": "Registers a domain name for a specified number of years.",
"parameters": {
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "The full domain name to register, e.g. 'example.com'"
},
"years": {
"type": "integer",
"description": "Number of years to register the domain for. Defaults to 1.",
"default": 1
}
},
"required": ["domain"],
"additionalProperties": False
}
},
{
"type": "function",
"name": "create_dns_record",
"description": "Creates a DNS record for a domain.",
"parameters": {
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "The domain name to create the DNS record for, e.g. 'example.com'"
},
"record_type": {
"type": "string",
"description": "The DNS record type, e.g. 'A', 'CNAME', 'MX', 'TXT'",
"enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV"]
},
"host": {
"type": "string",
"description": "The host/subdomain for the record, e.g. 'www' or '@' for the root domain"
},
"answer": {
"type": "string",
"description": "The value/target of the DNS record, e.g. an IP address for A records or a hostname for CNAME records"
},
"ttl": {
"type": "integer",
"description": "Time-to-live in seconds. Defaults to 300.",
"default": 300
}
},
"required": ["domain", "record_type", "host", "answer"],
"additionalProperties": False
}
}
]Create the client and tool map, then build the run loop:
import json
import openai
tool_map = {
"check_domain_availability_tool": check_domain_availability_tool,
"register_domain": register_domain,
"create_dns_record": create_dns_record
}
client = openai.OpenAI()
def handle_tool_calls(response):
"""Process any tool calls from the response and return results."""
results = []
for item in response.output:
if item.type == "function_call":
fn = tool_map.get(item.name)
args = json.loads(item.arguments)
result = fn(**args)
results.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(result)
})
return results
def run_agent(user_message: str, max_iterations: int = 50):
messages = [{"role": "user", "content": user_message}]
previous_response_id = None
for iteration in range(max_iterations):
request_params = {
"model": "gpt-4o",
"input": messages,
"tools": pythontools
}
# Link to previous response if we're continuing a conversation
if previous_response_id:
request_params["previous_response_id"] = previous_response_id
request_params["input"] = handle_tool_calls(response) # Only send tool results
response = client.responses.create(**request_params)
previous_response_id = response.id
tool_calls = [item for item in response.output if item.type == "function_call"]
if not tool_calls:
for item in response.output:
if item.type == "message":
print("Assistant:", item.content[0].text)
break
else:
print(f"Warning: agent stopped after reaching the maximum of {max_iterations} iterations.")
# Run the agent
run_agent("Find an available .com domain for a Python async job queue library, register it, and point it to 192.168.113.42.")The loop re-polls after submitting tool_outputs because the model may invoke additional tools before producing its final message. On a three-tool sequence (check, register, DNS), expect two to three requires_action cycles before the run reaches completed.
A completed run produces output similar to:
Assistant: The domain asyncqueuelib.com has been registered, and the A record has been set up to point to the IP address 192.168.113.42Production checklist: idempotency, rate limits, and error handling
Check availability before every registration call and maintain a local ledger (a SQLite table or Redis set is sufficient) of every domain your agent has registered. Query that ledger before calling the API. Registration is billable and irreversible, so the guard needs to catch duplicate attempts even across agent restarts.
Add a confirmation step before register_domain executes in any user-facing flow. In LangChain, use an interrupt() hook on the registration tool. In CrewAI, set human_input=True on the registration Task, as shown above. In OpenAI Responses, add an explicit confirmation requirement to the system instructions. Fully autonomous execution works for internal pipelines where you control all inputs, but anything user-facing should require an explicit approval signal before a domain purchase fires.
Retry network timeouts with exponential backoff (three attempts, cap at 30 seconds). A 409 conflict means the domain was taken between your availability check and the registration POST, so re-run availability on your fallback candidates. A 422 validation error always includes a field-level description in the response body that identifies exactly which registrant field is malformed, so log the full resp.text and surface it rather than swallowing the error.
Running the agent end-to-end
Get your name.com API token and run the first agent end-to-end. You pay only for the domains you register, with no subscription and no usage fees.
Autonomous domain agent FAQS
How does the name.com API authenticate, and how do I structure credentials for an agent?
The name.com API uses HTTP Basic Auth. Your credentials are your username and API token, Base64-encoded and passed as an Authorization header on every request. For agent use, store these in a secrets manager and inject them at runtime — never hardcode them in the function definitions the agent calls.
Why do I need a separate check_domain_availability_tool function for LangChain when the shared toolset already has check_domain_availability?
LangChain's @tool decorator requires each decorated function to be independently callable. Because check_domain_availability is also called internally by register_domain in the shared toolset, decorating the same function causes conflicts. The solution is a separate check_domain_availability_tool that wraps the same logic but exists only for LangChain tool use.
How do I prevent an AI agent from registering the same domain twice?
Maintain a local ledger — a SQLite table or Redis set — of every domain your agent has registered. Query it before every API call. Because registration is both billable and irreversible, this guard needs to survive agent restarts. Also call check_domain_availability before every registration attempt to catch names taken since your last check.
What's the difference between the LangChain, CrewAI, and OpenAI Responses implementations in this guide?
All three use the same Python wrapper functions — the difference is how you expose them and how the agent reasons. LangChain gives you a ReAct loop with streaming output and a recursion limit guard. CrewAI uses a sequential task structure with an explicit human confirmation gate before the billable registration step. OpenAI Responses uses explicit JSON function schemas and a polling loop that handles multi-turn tool calls. The shared toolset means you can adopt any of the three without rewriting your domain logic.
What do the 409 and 422 errors from the registration endpoint mean, and how should my agent handle them?
A 409 conflict means the domain was taken between your availability check and the registration POST — another registrant got there first. Your agent should re-run availability on fallback candidates. A 422 validation error means a registrant contact field is malformed; the response body always includes a field-level description of what's wrong. Log the full response text and surface it to the agent rather than swallowing the exception, so it can reason about the failure.
