DIY Cloudflare Automation: API Tokens, Notifications, Health Checks
A scoped API token and three notification policies will take you further than most teams expect. Before you evaluate any third-party tooling, it's worth knowing how much Cloudflare automation you can build with what's already in the product. The honest answer is "quite a lot." The equally honest follow-up is "until you're maintaining a pile of cron jobs and shell scripts nobody remembers writing."
Part one of this series mapped the config-sprawl problem: zones collecting years of page rules and WAF exceptions, DNS records without owners, settings drifting between zones, and security events nobody reviews. This part is the DIY response, automating detection and routine action with Cloudflare's native features only. Every command runs against api.cloudflare.com/client/v4 and is ready to paste.
01Tool 1
The API
DNS, WAF rules and cache purges as code, plus nightly diffs.
02Tool 2
Notifications
WAF spikes, certificate problems and origin health pushed to a webhook.
03Tool 3
Health checks
Multi-region probes, and pool failover with Load Balancing.
04Tool 4
Audit logs
Every change with actor and timestamp, the raw material for correlation.
Step 0: Scoped API tokens, the security baseline
Everything else depends on API access, so get the credential model right first. Cloudflare has two options: the legacy Global API Key (full access to everything in your account, with no expiry) and API tokens (scoped to specific permissions and zones, with optional IP restrictions and expiry). There's no good reason to use the Global API Key for automation in 2026. Create a token instead: dash.cloudflare.com → My Profile → API Tokens → Create Token.
Two rules will save you an incident later:
- One token per job. Your DNS sync script gets
Zone → DNS → Editon one zone. Your audit script gets read-only permissions across zones. When a token leaks, the blast radius is one job, not your whole edge. - Read-only by default. Start every token with Read permissions and add Edit only when a script actually writes.
Verify a token before wiring it into anything:
curl "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $CF_API_TOKEN"
A healthy response returns "status": "active". From here on, $CF_API_TOKEN, $ZONE_ID and $ACCOUNT_ID are assumed to be set. Zone IDs are on each zone's Overview page, or from the API:
curl "https://api.cloudflare.com/client/v4/zones?per_page=50" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| jq -r '.result[] | "\(.id) \(.name)"'
Tool 1: The API, with DNS, WAF rules and cache as code
The Cloudflare API covers essentially everything the dashboard does. Three areas account for most routine operations work.
DNS records
Start with an inventory. It's the export you wished you had in part one when nobody could say who owned a record:
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?per_page=100" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| jq -r '.result[] | [.type, .name, .content, (.proxied|tostring)] | @tsv'
Write this to a file nightly and diff it against yesterday's, and you have a working DNS change detector in four lines of cron. Creating a record is one POST ("ttl": 1 means Auto):
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"type":"A","name":"api.example.com","content":"203.0.113.10","ttl":1,"proxied":true}'
Updates use PATCH .../dns_records/$RECORD_ID with the same body shape. The classic automation here is a failover script that repoints an A record when the primary origin dies. It works, but note that you decided what "dead" means, in whatever logic the script runs at 3 a.m.
WAF custom rules
WAF custom rules live in the Rulesets API under the http_request_firewall_custom phase. List what you already have first, since most zones carry rules nobody can explain:
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_firewall_custom/entrypoint" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| jq '.result.rules[] | {id, description, action, expression}'
Adding a rule is a POST to that ruleset. Prefer managed_challenge over block for anything you're not sure about. It filters bots without hard-failing real users:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID/rules" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"action": "managed_challenge",
"expression": "(http.request.uri.path contains \"/wp-login.php\")",
"description": "Challenge WordPress login probes"
}'
Run the same listing on every zone and diff the results, and you have a WAF drift detector. When staging's rules stop matching production's, you'll know before an attacker does.
Cache purge
Purging by URL after a deploy is the most common Cloudflare automation, and it's one call:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/assets/app.js","https://example.com/index.html"]}'
You can also purge by prefix, tag or host (tags and hosts need Enterprise), or send {"purge_everything":true}. Treat that last one like rm -rf: it works instantly and your origin absorbs the stampede. Put the targeted version in CI as a post-deploy step and the "why is the old version still live" tickets mostly stop.
Tool 2: Notifications, webhooks for the signals that matter
Part one named the signals worth watching: WAF spikes, certificate problems, origin health. Cloudflare Notifications can push all three to a webhook (Slack, PagerDuty or your own endpoint). In the dashboard: Manage Account → Notifications.
Register the webhook destination first:
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/alerting/v3/destinations/webhooks" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"name":"ops-webhook","url":"https://hooks.example.com/cloudflare"}'
Then create policies against it. These three pay for themselves right away:
| Notification (dashboard name) | Alert type (API) | What it catches |
|---|---|---|
| Security Events Alert | clickhouse_alert_fw_anomaly | Spikes in WAF and firewall event volume |
| Universal SSL Alert | universal_ssl_event_type | Certificate validation and expiry problems |
| Health Check Status Notification | health_check_status_notification | Origin flipping unhealthy |
Build the body with jq so the webhook ID (returned by the destinations call) and zone ID are filled in by the shell:
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/alerting/v3/policies" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "$(jq -n --arg wh "$WEBHOOK_ID" --arg zone "$ZONE_ID" '{
name: "WAF spike to ops webhook",
enabled: true,
alert_type: "clickhouse_alert_fw_anomaly",
mechanisms: {webhooks: [{id: $wh}]},
filters: {zones: [$zone]}
}')"
Be clear about what a notification is: a fact, delivered. "Firewall events spiked on zone X" arrives in Slack with no hint of whether it's a credential-stuffing run, a scraper or your own load test. That classification is still yours, every time the webhook fires.
Tool 3: Health checks and load balancer monitors
Standalone Health Checks (Pro plan and up) probe your origin from several regions and feed the health-status notification above:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/healthchecks" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"name": "origin-health",
"address": "origin.example.com",
"type": "HTTPS",
"check_regions": ["WNAM", "ENAM", "WEU"],
"http_config": {"method": "GET", "path": "/healthz", "expected_codes": ["200"]},
"interval": 60,
"retries": 2,
"timeout": 5
}'
Set retries to at least 2. Failing over on a single transient blip is how a non-incident becomes an incident. If you run Load Balancing, monitors (GET /accounts/$ACCOUNT_ID/load_balancers/monitors) do the same probing with teeth: an unhealthy origin is pulled from the pool automatically. That's native automation at its best, for the one failure mode you configured in advance.
Tool 4: Audit logs, reviewing what changed
Every dashboard click and API write lands in the audit log. Pull the last two weeks and see who changed what:
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/audit_logs?since=2026-09-23T00:00:00Z&per_page=100" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| jq -r '.result[] | [.when, .actor.email, .action.type, .resource.type] | @tsv'
Schedule this weekly and actually read it. The findings are predictable and useful: WAF rules turned off during an incident and never turned back on, DNS edits from a token you thought was retired, settings changed by someone who left the on-call rotation months ago. Connecting "origin errors started Tuesday" with "someone changed the SSL mode Tuesday" is the most valuable habit in Cloudflare operations, and the audit log is the raw material for it.
One WAF spike, investigated by hand
The Security Events Alert from Tool 2 fires at 2:07 a.m. for app.example.com. Before touching any rule, name the readings and what each predicts.
01Reading 1
Credential stuffing
One rule on the login path, a few ASNs, rotating IPs, no config change.
02Reading 2
Scanner
Many paths, many rules, one or two sources, short burst.
03Reading 3
Self-inflicted
Your own user agent being blocked, right after a rule or exception changed.
Group the events. The GraphQL Analytics API returns the spike broken down by rule, path, network and user agent:
curl -s "https://api.cloudflare.com/client/v4/graphql" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"query":"query($zone:String!,$since:Time!,$until:Time!){viewer{zones(filter:{zoneTag:$zone}){firewallEventsAdaptiveGroups(limit:20,orderBy:[count_DESC],filter:{datetime_geq:$since,datetime_leq:$until}){count dimensions{ruleId action clientRequestPath clientAsn userAgent}}}}}","variables":{"zone":"'"$ZONE_ID"'","since":"2026-10-07T01:30:00Z","until":"2026-10-07T02:30:00Z"}}' \
| jq '.data.viewer.zones[0].firewallEventsAdaptiveGroups'
If almost every event is one managed rule on POST /login, spread across a handful of hosting ASNs with generic user agents, that's reading one. Hits across dozens of paths from one or two sources in a short burst is reading two.
Check what changed. Run the audit log query from Tool 4 for the last 24 hours. A deleted exception or edited rule a few minutes before the spike, plus your own mobile app's user agent in the grouped events, is reading three.
Decide. Reading one with the WAF holding and the origin healthy is a morning task: add a rate-limiting rule on /login. Reading two usually needs nothing. Reading three needs the exception restored now, because the WAF is blocking your own customers.
That's two queries and some careful reading, maybe 30 to 40 minutes at 2 a.m. for someone who knows the GraphQL schema. The notification did its job in seconds. Everything after it was a person.
What each native tool automates, and what it leaves
| Native tool | What it automates | What it still leaves to a person |
|---|---|---|
| Scoped tokens | Least-privilege access per job | Rotating and retiring them |
| API + cron diffs | DNS and WAF drift detection | Deciding whether a change was wanted |
| Cache purge in CI | Fresh content after every deploy | Nothing, for the targeted version |
| Notifications | Delivery of spikes, cert and health events | Classifying what the spike is |
| Health checks and LB monitors | Failover for one predefined failure | Every failure that isn't "origin down" |
| Audit logs | A complete record of changes | Connecting a change to a symptom |
The right-hand column is where the hours go. Cloudflare's native tools detect and execute well. The step between, reading the evidence and choosing what to do, has no native feature.
The ceiling: the API executes, it doesn't decide
Run everything above and you'll have a solid setup: scoped tokens, DNS and WAF drift detection, CI-driven cache purges, multi-region health probes and alerts in Slack. You'll also have reached the structural limit of native Cloudflare automation.
01Detect
Webhook fires
Firewall events spiked on a zone. A fact, delivered in seconds.
02Investigate
By hand
Query events, group by rule and source, read the audit log.
03Decide
By hand, again
Ignore, challenge, block, or restore what someone deleted.
04Execute
API call
The rule change or DNS edit, once a person has chosen it.
The API only executes decisions you already made. The failover script repoints DNS because you decided in advance what "down" means. The WAF rule blocks the path you picked out. Nothing native investigates a new situation.
Notifications deliver facts, not conclusions. A WAF-spike webhook at 2 a.m. still needs a person to query events, classify the traffic, decide between challenge and block, and write the rule. Detection takes seconds. The response is still measured in engineer hours.
The scripts become the sprawl. Each cron job, diff script and webhook handler is code with an owner, a token to rotate and edge cases found in production. Six months in, your automation needs its own audit.
Correlation is manual. The audit log knows what changed. The health check knows the origin is failing. Nothing native connects the two.
Where approval fits
Part three adds the investigating layer, and it splits actions the same way you already split tokens: you decide which writes (a DNS edit, a WAF change, a purge) run on their own and which wait for a named person. If you've already decided which scripts are safe to run unattended, you already think this way.
What comes next
The missing layer is one that investigates: it reads the WAF spike, checks it against recent config changes and origin health, and proposes the fix. That's what CloudThinker does on top of Cloudflare, starting from a read-only scoped token, with every action gated by Auto Mode. Part three walks through it.
To try it on your own zones, start with the investigation you just did by hand:
"Why did firewall events spike on app.example.com around 2 a.m.? Group the events by rule, path and network, and check the audit log for changes in the hour before. Propose only, change nothing."
Try CloudThinker free with 100 premium credits and no card required.
