Skip to content

Instantly share code, notes, and snippets.

@alexios-angel
Last active February 1, 2026 17:18
Show Gist options
  • Select an option

  • Save alexios-angel/fd236e27014e311e934b58c43e7cbda2 to your computer and use it in GitHub Desktop.

Select an option

Save alexios-angel/fd236e27014e311e934b58c43e7cbda2 to your computer and use it in GitHub Desktop.
Get domain/zone information - Cloudflare
#!/usr/bin/env python3
"""
Cloudflare Domain Information Exporter
Exports domain information from Cloudflare to a CSV file including:
- Domain Name
- Status (Active/Inactive)
- Registrar
- Expires
- Set to Auto Renew?
- Number of Unique Visitors (last 30 days)
- Zone ID
- Plan (Free/Business/Enterprise)
Usage:
python cloudflare_domain_export.py --api-token YOUR_API_TOKEN --account-id YOUR_ACCOUNT_ID [-o output.csv]
Or using environment variables:
export CLOUDFLARE_API_TOKEN=YOUR_API_TOKEN
export CLOUDFLARE_ACCOUNT_ID=YOUR_ACCOUNT_ID
python cloudflare_domain_export.py [-o output.csv]
Required API Token Permissions:
- Zone:Read (for all zones)
- Analytics:Read (for unique visitor stats via GraphQL)
- Registrar:Read (for domain expiry/auto-renew info - if domains registered with CF)
---------------------------------------------------------------------------------------------
Copyright 2026 Alexios Angel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import argparse
import csv
import os
import sys
from datetime import datetime, timedelta, timezone
try:
import cloudflare
from cloudflare import Cloudflare
except ImportError:
print("Error: cloudflare package not installed.")
print("Install it with: pip install cloudflare")
sys.exit(1)
try:
import requests
except ImportError:
print("Error: requests package not installed.")
print("Install it with: pip install requests")
sys.exit(1)
try:
from whois import whois as whois_lookup
except ImportError:
print("Error: python-whois package not installed.")
print("Install it with: pip install python-whois")
sys.exit(1)
def get_all_zones(client: Cloudflare) -> list:
"""
Fetch all zones (domains) from Cloudflare account using auto-pagination.
The official SDK provides auto-paginating iterators.
"""
zones = []
# The SDK auto-paginates when iterating
for zone in client.zones.list():
zones.append(zone)
return zones
def get_unique_visitors_graphql(api_token: str, zone_id: str) -> str:
"""
Fetch unique visitor count for a zone using GraphQL API (last 30 days).
The old zone analytics API is deprecated; GraphQL is the current method.
"""
try:
end_date = datetime.now(timezone.utc).date()
start_date = end_date - timedelta(days=30)
headers = {
'Authorization': f'Bearer {api_token}',
'Content-Type': 'application/json'
}
# GraphQL query for unique visitors using httpRequests1dGroups
query = """
query GetUniqueVisitors($zoneTag: String!, $date_geq: String!, $date_lt: String!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequests1dGroups(
limit: 10000,
filter: { date_geq: $date_geq, date_lt: $date_lt }
) {
uniq {
uniques
}
}
}
}
}
"""
variables = {
'zoneTag': zone_id,
'date_geq': start_date.isoformat(),
'date_lt': end_date.isoformat()
}
response = requests.post(
'https://api.cloudflare.com/client/v4/graphql',
headers=headers,
json={'query': query, 'variables': variables},
timeout=30
)
if response.status_code == 200:
data = response.json()
if 'data' in data and data['data']:
zones_data = data['data'].get('viewer', {}).get('zones', [])
if zones_data:
groups = zones_data[0].get('httpRequests1dGroups', [])
# Sum up unique visitors across all days
total_uniques = sum(
group.get('uniq', {}).get('uniques', 0)
for group in groups
)
return str(total_uniques)
return 'N/A'
except Exception as e:
# Analytics might not be available for all plans or zones
return 'N/A'
def get_registrar_domains(client: Cloudflare, account_id: str) -> dict:
"""
Fetch all domains registered with Cloudflare Registrar for the account.
Returns a dict mapping domain name to registration info.
"""
domains_info = {}
try:
# Use auto-pagination for registrar domains
for domain in client.registrar.domains.list(account_id=account_id):
domain_name = getattr(domain, 'name', None)
if not domain_name:
# Some responses use 'id' as the domain identifier
domain_name = getattr(domain, 'id', None)
if domain_name:
domains_info[domain_name] = {
'expires_at': getattr(domain, 'expires_at', None),
'auto_renew': getattr(domain, 'auto_renew', None),
'locked': getattr(domain, 'locked', None),
}
except cloudflare.NotFoundError:
# Account may not have registrar access
pass
except cloudflare.PermissionDeniedError:
# Token may not have registrar permissions
print("Warning: No permission to access Registrar API. Expiry/auto-renew data will be N/A.")
except Exception as e:
print(f"Warning: Could not fetch registrar domains: {e}")
return domains_info
def get_whois_info(domain_name: str) -> dict:
"""
Perform WHOIS lookup to get registrar and expiry info for non-Cloudflare domains.
Returns dict with 'registrar' and 'expires' keys.
"""
try:
w = whois_lookup(domain_name)
registrar = None
expires = None
# whois returns a dict-like object
# Get registrar
if 'registrar' in w and w['registrar']:
registrar = w['registrar']
# Get expiration date
if 'expiration_date' in w and w['expiration_date']:
exp_date = w['expiration_date']
# Sometimes returns a list of dates
if isinstance(exp_date, list):
exp_date = exp_date[0]
if hasattr(exp_date, 'strftime'):
expires = exp_date.strftime('%Y-%m-%d')
else:
expires = str(exp_date)[:10]
return {
'registrar': registrar,
'expires': expires
}
except Exception as e:
# WHOIS lookup can fail for various reasons
return {
'registrar': None,
'expires': None
}
def format_plan_name(plan) -> str:
"""Extract and format plan name."""
if plan is None:
return 'Unknown'
if hasattr(plan, 'name'):
return plan.name
elif isinstance(plan, dict):
return plan.get('name', 'Unknown')
return 'Unknown'
def format_status(status: str) -> str:
"""Format zone status to Active/Inactive."""
if status and status.lower() == 'active':
return 'Active'
return 'Inactive'
def export_domains_to_csv(api_token: str, account_id: str, output_file: str):
"""Main function to export domain information to CSV."""
# Initialize Cloudflare client with API token
client = Cloudflare(api_token=api_token)
print(f"Using Account ID: {account_id}")
print("Fetching zones from Cloudflare...")
zones = get_all_zones(client)
print(f"Found {len(zones)} zones")
# Fetch registrar domain info (for expiry and auto-renew)
print("Fetching registrar domain information...")
registrar_domains = get_registrar_domains(client, account_id)
print(f"Found {len(registrar_domains)} domains in Cloudflare Registrar")
# Prepare CSV data
csv_data = []
for i, zone in enumerate(zones, 1):
domain_name = zone.name
print(f"Processing [{i}/{len(zones)}]: {domain_name}")
# Basic zone info
zone_id = zone.id
status = format_status(zone.status)
plan = format_plan_name(zone.plan)
original_registrar = getattr(zone, 'original_registrar', None)
# Get domain registration info from pre-fetched registrar data
reg_info = registrar_domains.get(domain_name, {})
is_cloudflare_registrar = bool(reg_info)
# Initialize values
registrar = None
expires_str = 'N/A'
auto_renew_str = 'N/A'
if is_cloudflare_registrar:
# Domain is registered with Cloudflare
registrar = 'Cloudflare'
expires = reg_info.get('expires_at')
auto_renew = reg_info.get('auto_renew')
# Format expiry date
if expires:
if isinstance(expires, str):
expires_str = expires[:10]
elif hasattr(expires, 'strftime'):
expires_str = expires.strftime('%Y-%m-%d')
else:
expires_str = str(expires)[:10]
# Format auto-renew
if auto_renew is not None:
auto_renew_str = 'Yes' if auto_renew else 'No'
else:
# Domain is not registered with Cloudflare
# First try the original_registrar from zone info
if original_registrar:
registrar = original_registrar
# If registrar is still unknown, do WHOIS lookup
if not registrar or registrar.lower() == 'unknown':
print(f" → WHOIS lookup for {domain_name}...")
whois_info = get_whois_info(domain_name)
if whois_info.get('registrar'):
registrar = whois_info['registrar']
if whois_info.get('expires'):
expires_str = whois_info['expires']
if not registrar:
registrar = 'Unknown'
# Get analytics (unique visitors) via GraphQL
unique_visitors = get_unique_visitors_graphql(api_token, zone_id)
csv_data.append({
'Domain Name': domain_name,
'Status': status,
'Registrar': registrar,
'Expires': expires_str,
'Set to Auto Renew?': auto_renew_str,
'Number of Unique Visitors': unique_visitors,
'Zone ID': zone_id,
'Plan': plan
})
# Write to CSV
fieldnames = [
'Domain Name',
'Status',
'Registrar',
'Expires',
'Set to Auto Renew?',
'Number of Unique Visitors',
'Zone ID',
'Plan'
]
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(csv_data)
print(f"\nExport complete! Data saved to: {output_file}")
print(f"Total domains exported: {len(csv_data)}")
def main():
parser = argparse.ArgumentParser(
description='Export Cloudflare domain information to CSV',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python cloudflare_domain_export.py --api-token YOUR_API_TOKEN --account-id YOUR_ACCOUNT_ID
python cloudflare_domain_export.py -t YOUR_API_TOKEN -a YOUR_ACCOUNT_ID -o my_domains.csv
Using environment variables:
export CLOUDFLARE_API_TOKEN=YOUR_API_TOKEN
export CLOUDFLARE_ACCOUNT_ID=YOUR_ACCOUNT_ID
python cloudflare_domain_export.py
Creating an API Token:
1. Go to https://dash.cloudflare.com/profile/api-tokens
2. Click "Create Token"
3. Use "Custom token" and add these permissions:
- Zone > Zone > Read
- Zone > Analytics > Read
- Account > Registrar: Domains > Read (for expiry/auto-renew info)
4. Set Zone Resources to "Include > All zones"
Finding your Account ID:
1. Go to https://dash.cloudflare.com
2. Select any domain in your account
3. Find "Account ID" in the right sidebar under "API"
Notes:
- Uses API Token authentication (recommended over Global API Key)
- Domain registration info (expiry, auto-renew) is only available for
domains registered through Cloudflare Registrar
- For non-Cloudflare domains, WHOIS lookup is performed to get registrar info
- Unique visitors are fetched via GraphQL API (last 30 days)
- The script handles pagination automatically for accounts with many domains
"""
)
parser.add_argument(
'--api-token', '-t',
help='Cloudflare API Token (or set CLOUDFLARE_API_TOKEN env var)',
default=os.environ.get('CLOUDFLARE_API_TOKEN')
)
parser.add_argument(
'--account-id', '-a',
help='Cloudflare Account ID (or set CLOUDFLARE_ACCOUNT_ID env var)',
default=os.environ.get('CLOUDFLARE_ACCOUNT_ID')
)
parser.add_argument(
'-o', '--output',
help='Output CSV file path (default: cloudflare_domains.csv)',
default='cloudflare_domains.csv'
)
args = parser.parse_args()
# Validate API token
if not args.api_token:
print("Error: API token is required.")
print("Provide it via --api-token argument or CLOUDFLARE_API_TOKEN environment variable.")
print("\nTo create an API token:")
print(" 1. Go to https://dash.cloudflare.com/profile/api-tokens")
print(" 2. Click 'Create Token'")
print(" 3. Add permissions: Zone:Read, Analytics:Read, Registrar:Read")
sys.exit(1)
# Validate Account ID
if not args.account_id:
print("Error: Account ID is required.")
print("Provide it via --account-id argument or CLOUDFLARE_ACCOUNT_ID environment variable.")
print("\nTo find your Account ID:")
print(" 1. Go to https://dash.cloudflare.com")
print(" 2. Select any domain")
print(" 3. Find 'Account ID' in the right sidebar under 'API'")
sys.exit(1)
try:
export_domains_to_csv(args.api_token, args.account_id, args.output)
except cloudflare.APIConnectionError as e:
print(f"Error connecting to Cloudflare API: {e}")
sys.exit(1)
except cloudflare.AuthenticationError as e:
print(f"Authentication failed. Check your API token: {e}")
sys.exit(1)
except cloudflare.PermissionDeniedError as e:
print(f"Permission denied. Ensure your token has required permissions: {e}")
sys.exit(1)
except cloudflare.APIStatusError as e:
print(f"Cloudflare API error: {e}")
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}")
raise
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment