skills/jezweb/claude-skills/cloudflare-api

cloudflare-api

SKILL.md

Cloudflare API

Hit the Cloudflare REST API directly when wrangler CLI or MCP servers aren't the right tool. For bulk operations, fleet-wide changes, and features that wrangler doesn't expose.

When to Use This Instead of Wrangler or MCP

Use case Wrangler MCP This skill
Deploy a Worker Yes Yes No
Create a D1 database Yes Yes No
Bulk update 50 DNS records Slow (one at a time) Slow (one tool call each) Yes — batch script
Custom hostnames for white-label No Partial Yes
Email routing rules No Partial Yes
WAF/firewall rules No Yes but verbose Yes — direct API
Redirect rules in bulk No One at a time Yes — batch script
Zone settings across 20 zones No 20 separate calls Yes — fleet script
Cache purge by tag/prefix No Yes Yes (when scripting)
Worker route management Limited Yes Yes (when bulk)
Analytics/logs query No Partial Yes — GraphQL
D1 query/export across databases One DB at a time One DB at a time Yes — cross-DB scripts
R2 bulk object operations No One at a time Yes — S3 API + batch
KV bulk read/write/delete One at a time One at a time Yes — bulk endpoints
Vectorize query/delete No Via Worker only Yes — direct API
Queue message injection No Via Worker only Yes — direct API
Audit all resources in account No Tedious Yes — inventory script

Rule of thumb: Single operations → MCP or wrangler. Bulk/fleet/scripted → API directly.

Auth Setup

API Token (recommended)

Create a scoped token at: Dashboard → My Profile → API Tokens → Create Token

# Store it
export CLOUDFLARE_API_TOKEN="your-token-here"

# Test it
curl -s "https://api.cloudflare.com/client/v4/user/tokens/verify" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.success'

Token scopes: Always use minimal permissions. Common presets:

  • "Edit zone DNS" — for DNS operations
  • "Edit zone settings" — for zone config changes
  • "Edit Cloudflare Workers" — for Worker route management
  • "Read analytics" — for GraphQL analytics

Account and Zone IDs

# List your zones (find zone IDs)
curl -s "https://api.cloudflare.com/client/v4/zones?per_page=50" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {name, id}'

# Get zone ID by domain name
ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result[0].id')

Store IDs in environment or a config file — don't hardcode them in scripts.

Workflows

Bulk DNS Operations

Add/update many records at once (e.g. migrating a domain, setting up a new client):

# Pattern: read records from a file, create in batch
while IFS=',' read -r type name content proxied; do
  curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
    -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"type\":\"$type\",\"name\":\"$name\",\"content\":\"$content\",\"proxied\":$proxied,\"ttl\":1}" \
    | jq '{name: .result.name, id: .result.id, success: .success}'
  sleep 0.25  # Rate limit: 1200 req/5min
done < dns-records.csv

Export all records from a zone (backup or migration):

curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?per_page=100" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  | jq -r '.result[] | [.type, .name, .content, .proxied] | @csv' > dns-export.csv

Find and replace across records (e.g. IP migration):

OLD_IP="203.0.113.1"
NEW_IP="198.51.100.1"

# Find records pointing to old IP
RECORDS=$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?content=$OLD_IP" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result[].id')

# Update each one
for RECORD_ID in $RECORDS; do
  curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
    -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"content\":\"$NEW_IP\"}" | jq '.success'
done

Custom Hostnames (White-Label Client Domains)

For SaaS apps where clients use their own domain (e.g. app.clientdomain.com → your Worker):

# Create custom hostname
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/custom_hostnames" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "hostname": "app.clientdomain.com",
    "ssl": {
      "method": "http",
      "type": "dv",
      "settings": {
        "min_tls_version": "1.2"
      }
    }
  }' | jq '{id: .result.id, status: .result.status, ssl_status: .result.ssl.status}'

# List custom hostnames
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/custom_hostnames?per_page=50" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  | jq '.result[] | {hostname, status, ssl_status: .ssl.status}'

# Check status (client needs to add CNAME)
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/custom_hostnames/$HOSTNAME_ID" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result.status'

Client setup: They add a CNAME: app.clientdomain.com → your-worker.your-domain.com

Email Routing Rules

# Enable email routing on zone
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/email/routing/enable" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

# Create a routing rule (forward info@ to a real address)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/email/routing/rules" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Forward info@",
    "enabled": true,
    "matchers": [{"type": "literal", "field": "to", "value": "info@example.com"}],
    "actions": [{"type": "forward", "value": ["real-inbox@gmail.com"]}]
  }' | jq '.success'

# Create catch-all rule
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/email/routing/rules" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Catch-all",
    "enabled": true,
    "matchers": [{"type": "all"}],
    "actions": [{"type": "forward", "value": ["catchall@company.com"]}]
  }' | jq '.success'

# List rules
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/email/routing/rules" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {name, enabled, matchers, actions}'

Cache Purge

# Purge everything (nuclear option)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"purge_everything": true}'

# Purge specific URLs
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files": ["https://example.com/styles.css", "https://example.com/app.js"]}'

# Purge by cache tag (requires Enterprise or cache tag headers)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["product-123", "homepage"]}'

# Purge by prefix
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefixes": ["https://example.com/images/"]}'

Redirect Rules (Bulk)

# Create a redirect rule
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "expression": "(http.request.uri.path eq \"/old-page\")",
        "description": "Redirect old-page to new-page",
        "action": "redirect",
        "action_parameters": {
          "from_value": {
            "target_url": {"value": "https://example.com/new-page"},
            "status_code": 301
          }
        }
      }
    ]
  }'

For bulk redirects (301s from a CSV), generate the rules array programmatically:

import json, csv

rules = []
with open('redirects.csv') as f:
    for row in csv.reader(f):
        old_path, new_url = row
        rules.append({
            "expression": f'(http.request.uri.path eq "{old_path}")',
            "description": f"Redirect {old_path}",
            "action": "redirect",
            "action_parameters": {
                "from_value": {
                    "target_url": {"value": new_url},
                    "status_code": 301
                }
            }
        })
print(json.dumps({"rules": rules}, indent=2))

Zone Settings (Fleet-Wide)

Apply the same settings across multiple zones:

# Settings to apply
SETTINGS='{"value":"full"}'  # SSL mode: full (strict)

# Get all active zones
ZONES=$(curl -s "https://api.cloudflare.com/client/v4/zones?status=active&per_page=50" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result[].id')

# Apply to each zone
for ZONE in $ZONES; do
  curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE/settings/ssl" \
    -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$SETTINGS" | jq "{zone: .result.id, success: .success}"
  sleep 0.25
done

Common fleet settings:

  • ssl — "full" or "strict"
  • min_tls_version — "1.2"
  • always_use_https — "on"
  • security_level — "medium"
  • browser_cache_ttl — 14400

WAF / Firewall Rules

# Create a WAF custom rule (block by country)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_firewall_custom/entrypoint" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [{
      "expression": "(ip.geoip.country in {\"RU\" \"CN\"})",
      "action": "block",
      "description": "Block traffic from RU and CN"
    }]
  }'

# Rate limiting rule
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_ratelimit/entrypoint" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [{
      "expression": "(http.request.uri.path contains \"/api/\")",
      "action": "block",
      "ratelimit": {
        "characteristics": ["ip.src"],
        "period": 60,
        "requests_per_period": 100
      },
      "description": "Rate limit API to 100 req/min per IP"
    }]
  }'

Worker Routes

# List routes
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {pattern, id}'

# Create route
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pattern": "api.example.com/*", "script": "my-worker"}'

# Delete route
curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes/$ROUTE_ID" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Analytics (GraphQL)

# Worker analytics (requests, errors, CPU time)
curl -s -X POST "https://api.cloudflare.com/client/v4/graphql" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ viewer { zones(filter: {zoneTag: \"'$ZONE_ID'\"}) { httpRequests1dGroups(limit: 7, filter: {date_gt: \"2026-03-10\"}) { dimensions { date } sum { requests pageViews } } } } }"
  }' | jq '.data.viewer.zones[0].httpRequests1dGroups'

Rate Limits

Endpoint Limit
Most API calls 1200 requests / 5 minutes
DNS record operations 1200 / 5 min (shared with above)
Cache purge 1000 purge calls / day
Zone creation 5 per minute

In scripts: Add sleep 0.25 between calls for sustained operations. Use p-limit or xargs -P 4 for controlled parallelism.

Script Generation

When the user describes what they need, generate a script in .jez/scripts/ that:

  • Reads API token from environment (never hardcode)
  • Handles pagination for list operations
  • Includes error checking (jq '.success' after each call)
  • Adds rate limit sleep between calls
  • Logs what it does
  • Supports --dry-run where possible

Prefer curl + jq for simple operations. Use Python for complex logic (pagination loops, error handling, CSV processing). Use TypeScript with the cloudflare npm package for type safety in larger scripts.

API Reference

Base URL: https://api.cloudflare.com/client/v4/

Full docs: https://developers.cloudflare.com/api/

The API follows a consistent pattern:

  • GET /zones — list
  • POST /zones — create
  • GET /zones/:id — read
  • PATCH /zones/:id — update
  • DELETE /zones/:id — delete
  • PUT /zones/:id/settings/:name — update setting

Every response has { success: bool, errors: [], messages: [], result: {} }.

Reference Files

When Read
D1, R2, KV, Workers, Vectorize, Queues API patterns references/developer-platform-api.md
Weekly Installs
118
GitHub Stars
661
First Seen
9 days ago
Installed on
opencode110
kimi-cli109
gemini-cli109
amp109
cline109
github-copilot109