Infrastructure-as-code tools are powerful, but sometimes you need more flexibility. Maybe you’re building a platform that provisions domains for customers. Maybe you need DNS changes triggered by application events. Maybe you want a custom dashboard that shows DNS status across all your domains.
This is where DNS APIs come in. Every major DNS provider exposes a REST (or REST-like) API for full programmatic control. Let’s explore the most important ones.
Cloudflare API
Cloudflare’s API is one of the most developer-friendly DNS APIs available. It’s well-documented, consistent, and powerful.
Authentication
# Option 1: API Token (recommended — scoped permissions)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.cloudflare.com/client/v4/zones
# Option 2: API Key + Email (legacy — full account access)
curl -H "X-Auth-Key: YOUR_API_KEY" \
-H "X-Auth-Email: you@example.com" \
https://api.cloudflare.com/client/v4/zones
Always use API tokens with scoped permissions. An API token with Zone.DNS:Edit for a specific zone can’t accidentally delete your other zones.
Listing DNS Records
$ curl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_TOKEN}" | jq '.result[] | {name, type, content, ttl}'
{
"name": "example.com",
"type": "A",
"content": "203.0.113.50",
"ttl": 3600
}
{
"name": "www.example.com",
"type": "CNAME",
"content": "example.com",
"ttl": 3600
}
Creating a Record
$ curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"type": "A",
"name": "api.example.com",
"content": "203.0.113.60",
"ttl": 300,
"proxied": false
}'
Updating a Record
# First, get the record ID
$ RECORD_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=api.example.com&type=A" \
-H "Authorization: Bearer ${CF_TOKEN}" | jq -r '.result[0].id')
# Then update it
$ curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"type": "A",
"name": "api.example.com",
"content": "198.51.100.75",
"ttl": 300,
"proxied": false
}'
Deleting a Record
$ curl -X DELETE "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${CF_TOKEN}"
Batch Operations
Cloudflare doesn’t have a native batch endpoint for DNS records, but you can script it:
#!/bin/bash
# bulk-create-records.sh
ZONE_ID="your-zone-id"
CF_TOKEN="your-api-token"
records='[
{"type":"A","name":"app1","content":"10.0.1.1","ttl":300},
{"type":"A","name":"app2","content":"10.0.1.2","ttl":300},
{"type":"A","name":"app3","content":"10.0.1.3","ttl":300}
]'
echo "$records" | jq -c '.[]' | while read -r record; do
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d "$record"
echo ""
sleep 0.25 # Respect rate limits
done
AWS Route 53 API
Route 53’s API is more complex than Cloudflare’s but offers features like health checks, routing policies, and tight integration with other AWS services.
Using the AWS CLI
# List hosted zones
$ aws route53 list-hosted-zones --output json | jq '.HostedZones[] | {Name, Id}'
# List records in a zone
$ aws route53 list-resource-record-sets \
--hosted-zone-id Z1234567890 \
--output json | jq '.ResourceRecordSets[] | {Name, Type, TTL}'
# Create/update records (Route 53 uses "change batches")
$ aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "203.0.113.60"}]
}
}]
}'
Route 53 Change Batches
Route 53’s batch model is atomic — all changes in a batch succeed or fail together:
{
"Changes": [
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "10.0.1.1"}]
}
},
{
"Action": "DELETE",
"ResourceRecordSet": {
"Name": "old-app.example.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "10.0.1.99"}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "www.example.com",
"Type": "CNAME",
"TTL": 3600,
"ResourceRecords": [{"Value": "example.com"}]
}
}
]
}
UPSERT is particularly useful — it creates the record if it doesn’t exist, or updates it if it does. No need to check first.
Route 53 Health Checks
Route 53 can monitor endpoints and automatically remove unhealthy records:
$ aws route53 create-health-check --caller-reference "$(date +%s)" \
--health-check-config '{
"IPAddress": "203.0.113.50",
"Port": 443,
"Type": "HTTPS",
"ResourcePath": "/health",
"RequestInterval": 30,
"FailureThreshold": 3
}'
RobotDomainSearch API for Domain Operations
RobotDomainSearch provides APIs for domain availability checks, WHOIS data, and DNS lookups — useful for building domain management workflows.
Domain Availability
# Check if a domain is available
$ curl -s "https://api.robotdomainsearch.com/v1/check?domain=example.com" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"domain": "example.com",
"available": false,
"premium": false,
"price": null
}
DNS Lookups
# Get DNS records for a domain
$ curl -s "https://api.robotdomainsearch.com/v1/dns?domain=example.com&type=A" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"domain": "example.com",
"records": [
{"type": "A", "value": "93.184.216.34", "ttl": 3600}
]
}
Building a Domain Setup Workflow
Combine RobotDomainSearch with a DNS provider API:
import requests
ROBOT_API = "https://api.robotdomainsearch.com/v1"
CF_API = "https://api.cloudflare.com/client/v4"
def provision_domain(domain, ip_address):
"""Full domain provisioning workflow."""
# Step 1: Check availability via RobotDomainSearch
check = requests.get(
f"{ROBOT_API}/check",
params={"domain": domain},
headers={"Authorization": f"Bearer {ROBOT_TOKEN}"}
).json()
if not check["available"]:
raise Exception(f"{domain} is not available")
# Step 2: (Register domain via registrar API — varies by provider)
# Step 3: Create DNS records via Cloudflare
zone = create_cloudflare_zone(domain)
records = [
{"type": "A", "name": domain, "content": ip_address, "ttl": 3600},
{"type": "CNAME", "name": f"www.{domain}", "content": domain, "ttl": 3600},
{"type": "TXT", "name": domain, "content": "v=spf1 -all", "ttl": 3600},
{"type": "TXT", "name": f"_dmarc.{domain}",
"content": "v=DMARC1; p=reject", "ttl": 3600},
{"type": "MX", "name": domain, "content": "0 .", "priority": 0, "ttl": 3600},
]
for record in records:
requests.post(
f"{CF_API}/zones/{zone['id']}/dns_records",
headers={"Authorization": f"Bearer {CF_TOKEN}"},
json=record
)
return {"domain": domain, "zone_id": zone["id"], "status": "provisioned"}
Webhooks for DNS Events
Some providers support webhooks that notify your application when DNS changes occur:
Cloudflare Notifications
Cloudflare doesn’t offer per-record webhooks, but provides notification policies for important events:
# Create a notification policy for DNS changes
$ curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/alerting/v3/policies" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "DNS Record Changes",
"alert_type": "zone_dns_record_update",
"enabled": true,
"mechanisms": {
"webhook": [{"id": "your-webhook-id"}]
},
"filters": {
"zones": ["your-zone-id"]
}
}'
Building Your Own DNS Change Watcher
If your provider doesn’t support webhooks, poll for changes:
import time
import hashlib
import json
import requests
def watch_dns_changes(zone_id, interval=60):
"""Poll for DNS changes and trigger webhooks."""
last_hash = None
while True:
records = fetch_all_records(zone_id)
current_hash = hashlib.sha256(
json.dumps(records, sort_keys=True).encode()
).hexdigest()
if last_hash and current_hash != last_hash:
changes = diff_records(last_records, records)
notify_webhook(changes)
last_hash = current_hash
last_records = records
time.sleep(interval)
Rate Limiting and API Best Practices
DNS APIs have rate limits. Hitting them in production is embarrassing and disruptive.
Cloudflare Rate Limits
- 1,200 requests per 5 minutes per API token
- That’s 4 requests/second sustained
- Burst is allowed but sustained high rates will get throttled
Route 53 Rate Limits
- 5 requests per second for
ChangeResourceRecordSets - 5 requests per second for
ListResourceRecordSets - Hard limits — exceeding them returns
Throttlingerrors
Best Practices
import time
from functools import wraps
def rate_limit(calls_per_second=4):
"""Simple rate limiter decorator."""
min_interval = 1.0 / calls_per_second
last_call = [0]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_call[0]
if elapsed < min_interval:
time.sleep(min_interval - elapsed)
result = func(*args, **kwargs)
last_call[0] = time.time()
return result
return wrapper
return decorator
@rate_limit(calls_per_second=3)
def update_dns_record(zone_id, record_id, data):
"""Rate-limited DNS update."""
return requests.put(
f"{CF_API}/zones/{zone_id}/dns_records/{record_id}",
headers={"Authorization": f"Bearer {CF_TOKEN}"},
json=data
)
Additional best practices:
- Cache API responses — Don’t re-fetch the full record list for every operation
- Use exponential backoff on rate limit errors (HTTP 429)
- Batch changes where the API supports it (Route 53’s change batches)
- Use separate API tokens per application/environment
- Log all API calls for auditing and debugging
- Implement idempotency — your automation should be safe to run multiple times
Key Takeaways
- Cloudflare’s API is the simplest for getting started — great docs, intuitive endpoints
- Route 53’s batch model is powerful for atomic multi-record changes
- RobotDomainSearch APIs complement DNS provider APIs for domain lifecycle management
- Build rate limiting into your clients from day one — don’t wait for 429s
- Combine APIs to build full-stack domain provisioning workflows
- Log everything and implement idempotent operations for production reliability