API-Driven DNS Management

Programmatic DNS control with Cloudflare, Route 53, and RobotDomainSearch APIs — building automation workflows from scratch.

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 Throttling errors

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