Updating DNS records through Cloudflare's web interface gets old fast. If you're managing multiple domains, running deployments, or just tired of clicking through menus, the Cloudflare API will save you hours of work. This guide shows you exactly how to automate your DNS management with working code examples.
What You Need
- A Cloudflare account with at least one domain
- Basic understanding of DNS (A records, CNAMEs, etc.)
- curl, Node.js, Python, or PHP installed
Getting Your API Credentials
You need an API token to interact with Cloudflare. Here's how to create one:
- Log into your Cloudflare dashboard
- Go to My Profile → API Tokens
- Click Create Token
- Use the "Edit zone DNS" template
- Set permissions to Zone → DNS → Edit
- Select the zones this token can access
- Create the token and save it immediately (you won't see it again)
Important Note: Store your API token securely. Never commit it to version control. Use environment variables instead.
API Fundamentals
All Cloudflare API requests go to: https://api.cloudflare.com/client/v4/
Every request needs these headers:
Authorization: Bearer YOUR_API_TOKENContent-Type: application/json
Step 1: Find Your Zone ID
Every domain in Cloudflare has a Zone ID, which us usually present in the address bar when navigating through your project on Cloudfalre. You'll need this for all DNS operations.
Run this once and save the ID:
curl
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"JavaScript
const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const BASE_URL = 'https://api.cloudflare.com/client/v4';
async function listZones() {
const response = await fetch(`${BASE_URL}/zones`, {
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
data.result.forEach(zone => {
console.log(`Domain: ${zone.name}, Zone ID: ${zone.id}`);
});
}
}
listZones();Python
import requests
import os
CLOUDFLARE_API_TOKEN = os.environ.get('CLOUDFLARE_API_TOKEN')
BASE_URL = 'https://api.cloudflare.com/client/v4'
def list_zones():
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
response = requests.get(f'{BASE_URL}/zones', headers=headers)
data = response.json()
if data['success']:
for zone in data['result']:
print(f"Domain: {zone['name']}, Zone ID: {zone['id']}")
list_zones()PHP
<?php
$token = $_ENV['CLOUDFLARE_API_TOKEN'];
$baseUrl = 'https://api.cloudflare.com/client/v4';
$ch = curl_init("$baseUrl/zones");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response, true);
if ($data['success']) {
foreach ($data['result'] as $zone) {
echo "Domain: {$zone['name']}, Zone ID: {$zone['id']}\n";
}
}
curl_close($ch);Tip: Save your Zone ID in an environment variable. You'll use it for every DNS operation.
Step 2: List All DNS Records
This will display all DNS records for your domain. You'll need record IDs for updates and deletions:
curl
ZONE_ID="your_zone_id_here"
curl -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"
# Filter by type (optional)
curl -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=A" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"JavaScript
async function listDNSRecords(zoneId, type = null) {
let url = `${BASE_URL}/zones/${zoneId}/dns_records`;
if (type) url += `?type=${type}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
data.result.forEach(record => {
console.log(`${record.type} | ${record.name} | ${record.content} | ID: ${record.id}`);
});
return data.result;
}
}
// List all records
listDNSRecords('your_zone_id');
// List only A records
listDNSRecords('your_zone_id', 'A');Python
def list_dns_records(zone_id, record_type=None):
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
url = f'{BASE_URL}/zones/{zone_id}/dns_records'
if record_type:
url += f'?type={record_type}'
response = requests.get(url, headers=headers)
data = response.json()
if data['success']:
for record in data['result']:
print(f"{record['type']} | {record['name']} | {record['content']} | ID: {record['id']}")
return data['result']
# List all records
list_dns_records('your_zone_id')
# List only A records
list_dns_records('your_zone_id', 'A')PHP
<?php
function listDNSRecords($zoneId, $type = null) {
global $token, $baseUrl;
$url = "$baseUrl/zones/$zoneId/dns_records";
if ($type) $url .= "?type=$type";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response, true);
if ($data['success']) {
foreach ($data['result'] as $record) {
echo "{$record['type']} | {$record['name']} | {$record['content']} | ID: {$record['id']}\n";
}
return $data['result'];
}
curl_close($ch);
}
// List all records
listDNSRecords('your_zone_id');
// List only A records
listDNSRecords('your_zone_id', 'A');Step 3: Create DNS Records
Creating new DNS records is straightforward. Here's what each field means:
| Field | Description | Example |
|---|---|---|
type |
Record type | A, AAAA, CNAME, TXT, MX |
name |
Full domain or subdomain | app.example.com or @ for root |
content |
The value (IP, domain, text) | 192.0.2.1 |
ttl |
Time to live (1 = automatic) | 1 or 3600 (seconds) |
proxied |
Use Cloudflare's proxy/CDN | true or false |
Create an "A" Record
An "A" record is the IP of the server you wish to point to. Add an "A" record with the following:
curl
ZONE_ID="your_zone_id_here"
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "A",
"name": "app.example.com",
"content": "192.0.2.1",
"ttl": 1,
"proxied": true
}'JavaScript
async function createARecord(zoneId, name, ip, proxied = true) {
const response = await fetch(`${BASE_URL}/zones/${zoneId}/dns_records`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'A',
name: name,
content: ip,
ttl: 1,
proxied: proxied
})
});
const data = await response.json();
if (data.success) {
console.log('A record created:', data.result);
return data.result;
} else {
console.error('Error:', data.errors);
}
}
createARecord('your_zone_id', 'app.example.com', '192.0.2.1');Python
def create_a_record(zone_id, name, ip, proxied=True):
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
record_data = {
'type': 'A',
'name': name,
'content': ip,
'ttl': 1,
'proxied': proxied
}
response = requests.post(
f'{BASE_URL}/zones/{zone_id}/dns_records',
headers=headers,
json=record_data
)
data = response.json()
if data['success']:
print('A record created:', data['result'])
return data['result']
else:
print('Error:', data['errors'])
create_a_record('your_zone_id', 'app.example.com', '192.0.2.1')PHP
<?php
function createARecord($zoneId, $name, $ip, $proxied = true) {
global $token, $baseUrl;
$data = [
'type' => 'A',
'name' => $name,
'content' => $ip,
'ttl' => 1,
'proxied' => $proxied
];
$ch = curl_init("$baseUrl/zones/$zoneId/dns_records");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
if ($result['success']) {
echo "A record created\n";
return $result['result'];
} else {
echo "Error: " . json_encode($result['errors']) . "\n";
}
curl_close($ch);
}
createARecord('your_zone_id', 'app.example.com', '192.0.2.1');Create Other Record Types
CNAMES are for the subdomains and redirecting "www" prefixes to the root domain.
CNAME Record (point www to root domain)
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "www.example.com",
"content": "example.com",
"ttl": 1,
"proxied": true
}'TXT Record (for domain verification, SPF, etc.)
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "TXT",
"name": "_verification.example.com",
"content": "verification-code-12345",
"ttl": 1
}'MX Record (for email)
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "MX",
"name": "example.com",
"content": "mail.example.com",
"priority": 10,
"ttl": 3600
}'Step 4: Update DNS Records
To update a record, you need its ID (from Step 2). Use PUT to replace the entire record or PATCH to update specific fields:
curl
# Full update (PUT)
RECORD_ID="your_record_id_here"
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "A",
"name": "app.example.com",
"content": "203.0.113.50",
"ttl": 1,
"proxied": true
}'
# Partial update (PATCH) - just change the IP
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"content": "203.0.113.100"
}'JavaScript
async function updateDNSRecord(zoneId, recordId, updates) {
// Use PATCH for partial updates
const response = await fetch(
`${BASE_URL}/zones/${zoneId}/dns_records/${recordId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
}
);
const data = await response.json();
if (data.success) {
console.log('Record updated:', data.result);
return data.result;
} else {
console.error('Error:', data.errors);
}
}
// Just update the IP
updateDNSRecord('zone_id', 'record_id', { content: '203.0.113.100' });Python
def update_dns_record(zone_id, record_id, updates):
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
# Use PATCH for partial updates
response = requests.patch(
f'{BASE_URL}/zones/{zone_id}/dns_records/{record_id}',
headers=headers,
json=updates
)
data = response.json()
if data['success']:
print('Record updated:', data['result'])
return data['result']
else:
print('Error:', data['errors'])
# Just update the IP
update_dns_record('zone_id', 'record_id', {'content': '203.0.113.100'})PHP
<?php
function updateDNSRecord($zoneId, $recordId, $updates) {
global $token, $baseUrl;
$ch = curl_init("$baseUrl/zones/$zoneId/dns_records/$recordId");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($updates));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
if ($result['success']) {
echo "Record updated\n";
return $result['result'];
} else {
echo "Error: " . json_encode($result['errors']) . "\n";
}
curl_close($ch);
}
// Just update the IP
updateDNSRecord('zone_id', 'record_id', ['content' => '203.0.113.100']);Step 5: Delete DNS Records
Deleting a record is simple. Just need the record ID:
curl
RECORD_ID="your_record_id_here"
curl -X DELETE "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"JavaScript
async function deleteDNSRecord(zoneId, recordId) {
const response = await fetch(
`${BASE_URL}/zones/${zoneId}/dns_records/${recordId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
if (data.success) {
console.log('Record deleted successfully');
return true;
} else {
console.error('Error:', data.errors);
return false;
}
}
deleteDNSRecord('zone_id', 'record_id');Python
def delete_dns_record(zone_id, record_id):
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
response = requests.delete(
f'{BASE_URL}/zones/{zone_id}/dns_records/{record_id}',
headers=headers
)
data = response.json()
if data['success']:
print('Record deleted successfully')
return True
else:
print('Error:', data['errors'])
return False
delete_dns_record('zone_id', 'record_id')PHP
<?php
function deleteDNSRecord($zoneId, $recordId) {
global $token, $baseUrl;
$ch = curl_init("$baseUrl/zones/$zoneId/dns_records/$recordId");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
if ($result['success']) {
echo "Record deleted successfully\n";
return true;
} else {
echo "Error: " . json_encode($result['errors']) . "\n";
return false;
}
curl_close($ch);
}
deleteDNSRecord('zone_id', 'record_id');Practical Example: Dynamic DNS Updater
Here's a complete script that updates your DNS when your IP changes. Perfect for home servers:
Python Version
#!/usr/bin/env python3
import requests
import os
import sys
CLOUDFLARE_API_TOKEN = os.environ.get('CLOUDFLARE_API_TOKEN')
ZONE_ID = os.environ.get('CLOUDFLARE_ZONE_ID')
RECORD_NAME = 'home.example.com'
BASE_URL = 'https://api.cloudflare.com/client/v4'
def get_public_ip():
"""Get current public IP address"""
try:
response = requests.get('https://api.ipify.org?format=json')
return response.json()['ip']
except:
print("Failed to get public IP")
sys.exit(1)
def get_dns_record(zone_id, record_name):
"""Find the DNS record by name"""
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
response = requests.get(
f'{BASE_URL}/zones/{zone_id}/dns_records?name={record_name}&type=A',
headers=headers
)
data = response.json()
if data['success'] and len(data['result']) > 0:
record = data['result'][0]
return record['id'], record['content']
return None, None
def update_dns_record(zone_id, record_id, record_name, new_ip):
"""Update DNS record with new IP"""
headers = {
'Authorization': f'Bearer {CLOUDFLARE_API_TOKEN}',
'Content-Type': 'application/json'
}
response = requests.patch(
f'{BASE_URL}/zones/{zone_id}/dns_records/{record_id}',
headers=headers,
json={'content': new_ip}
)
return response.json()
def main():
# Get current public IP
current_ip = get_public_ip()
print(f'Current public IP: {current_ip}')
# Get existing DNS record
record_id, existing_ip = get_dns_record(ZONE_ID, RECORD_NAME)
if not record_id:
print(f'DNS record {RECORD_NAME} not found. Create it first.')
sys.exit(1)
print(f'DNS currently points to: {existing_ip}')
# Update if IP changed
if current_ip != existing_ip:
print(f'IP changed! Updating DNS...')
result = update_dns_record(ZONE_ID, record_id, RECORD_NAME, current_ip)
if result['success']:
print(f'✓ DNS updated to {current_ip}')
else:
print(f'✗ Update failed: {result["errors"]}')
sys.exit(1)
else:
print('IP unchanged. Nothing to do.')
if __name__ == '__main__':
main()JavaScript Version
#!/usr/bin/env node
const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const ZONE_ID = process.env.CLOUDFLARE_ZONE_ID;
const RECORD_NAME = 'home.example.com';
const BASE_URL = 'https://api.cloudflare.com/client/v4';
async function getPublicIP() {
try {
const response = await fetch('https://api.ipify.org?format=json');
const data = await response.json();
return data.ip;
} catch (error) {
console.error('Failed to get public IP');
process.exit(1);
}
}
async function getDNSRecord(zoneId, recordName) {
const response = await fetch(
`${BASE_URL}/zones/${zoneId}/dns_records?name=${recordName}&type=A`,
{
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
if (data.success && data.result.length > 0) {
const record = data.result[0];
return { id: record.id, content: record.content };
}
return { id: null, content: null };
}
async function updateDNSRecord(zoneId, recordId, newIP) {
const response = await fetch(
`${BASE_URL}/zones/${zoneId}/dns_records/${recordId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ content: newIP })
}
);
return await response.json();
}
async function main() {
// Get current public IP
const currentIP = await getPublicIP();
console.log(`Current public IP: ${currentIP}`);
// Get existing DNS record
const { id: recordId, content: existingIP } = await getDNSRecord(ZONE_ID, RECORD_NAME);
if (!recordId) {
console.error(`DNS record ${RECORD_NAME} not found. Create it first.`);
process.exit(1);
}
console.log(`DNS currently points to: ${existingIP}`);
// Update if IP changed
if (currentIP !== existingIP) {
console.log('IP changed! Updating DNS...');
const result = await updateDNSRecord(ZONE_ID, recordId, currentIP);
if (result.success) {
console.log(`✓ DNS updated to ${currentIP}`);
} else {
console.error(`✗ Update failed:`, result.errors);
process.exit(1);
}
} else {
console.log('IP unchanged. Nothing to do.');
}
}
main();Setup tip: Add this script to cron (Linux/Mac) or Task Scheduler (Windows) to run every 5-10 minutes for automatic dynamic DNS.
Error Handling
Always check the success field in responses. Cloudflare provides detailed error messages:
JavaScript
async function safeAPICall(url, options) {
try {
const response = await fetch(url, options);
const data = await response.json();
if (!data.success) {
console.error('API Error:', data.errors);
data.errors.forEach(error => {
console.error(`Code ${error.code}: ${error.message}`);
});
return null;
}
return data.result;
} catch (error) {
console.error('Request failed:', error.message);
return null;
}
}Python
def safe_api_call(method, url, headers, json_data=None):
try:
if method == 'GET':
response = requests.get(url, headers=headers)
elif method == 'POST':
response = requests.post(url, headers=headers, json=json_data)
elif method == 'PATCH':
response = requests.patch(url, headers=headers, json=json_data)
elif method == 'DELETE':
response = requests.delete(url, headers=headers)
data = response.json()
if not data['success']:
print('API Error:', data['errors'])
for error in data['errors']:
print(f"Code {error['code']}: {error['message']}")
return None
return data['result']
except Exception as e:
print(f'Request failed: {e}')
return NoneRate Limits
Cloudflare allows 1200 requests per 5 minutes per API token. If you hit the limit, you'll get a 429 status code. The response includes a Retry-After header telling you how long to wait.
For production scripts, implement retry logic with exponential backoff. Most operations won't hit these limits unless you're doing bulk updates.
Security Best Practices
- Never hardcode tokens - Use environment variables
- Use .env files locally - Add to .gitignore immediately
- Create specific tokens - One per application, minimal permissions
- Rotate tokens regularly - Set calendar reminders
- Monitor token usage - Check Cloudflare's audit logs
Quick Reference
| Operation | Method | Endpoint |
|---|---|---|
| List zones | GET | /zones |
| List DNS records | GET | /zones/{zone_id}/dns_records |
| Create DNS record | POST | /zones/{zone_id}/dns_records |
| Update DNS record | PUT/PATCH | /zones/{zone_id}/dns_records/{record_id} |
| Delete DNS record | DELETE | /zones/{zone_id}/dns_records/{record_id} |
Common DNS Record Types
| Type | Purpose | Example Content |
|---|---|---|
| A | IPv4 address | 192.0.2.1 |
| AAAA | IPv6 address | 2001:db8::1 |
| CNAME | Alias to another domain | example.com |
| MX | Mail server (with priority) | mail.example.com |
| TXT | Text data (SPF, DKIM, verification) | v=spf1 include:_spf.example.com ~all |
Conclusion
The Cloudflare API makes DNS automation straightforward. Whether you're building deployment pipelines, managing multiple domains, or just tired of clicking through web interfaces, these API calls will save you hours.
Start simple - maybe just automate your most common DNS task. Once you see how much time it saves, you'll find yourself automating more and more of your DNS management.
Related Guides
- How to Move Your DNS from GoDaddy to Cloudflare - If you haven't migrated to Cloudflare yet, start here with the complete DNS migration guide
- Deploy Your Static Site to Cloudflare Workers - Once you have DNS automation down, learn how to deploy your entire site to Cloudflare's edge network
Official Cloudflare Resources
Fred
AUTHORFull-stack developer with 10+ years building production applications. I've been deploying to Cloudflare's edge network since Workers launched in 2017.

