How To

DIY Cloudflare Automation: API Tokens, Notifications, Health Checks

Part two of the Cloudflare series builds DIY automation with scoped API tokens, the API for DNS, WAF and cache purges, Notifications, health checks and the audit log. It investigates one WAF spike by hand with GraphQL and the audit log, then shows the ceiling: the API executes decisions you already made, and nothing native connects a symptom to the change behind it.

10 min read
Cover Image for DIY Cloudflare Automation: API Tokens, Notifications, Health Checks

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.

Four native tools, all behind one scoped API token per job.

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 → Edit on 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 Alertclickhouse_alert_fw_anomalySpikes in WAF and firewall event volume
Universal SSL Alertuniversal_ssl_event_typeCertificate validation and expiry problems
Health Check Status Notificationhealth_check_status_notificationOrigin 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.

Three readings of one Security Events Alert. Only the third one needs a fix tonight.

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 toolWhat it automatesWhat it still leaves to a person
Scoped tokensLeast-privilege access per jobRotating and retiring them
API + cron diffsDNS and WAF drift detectionDeciding whether a change was wanted
Cache purge in CIFresh content after every deployNothing, for the targeted version
NotificationsDelivery of spikes, cert and health eventsClassifying what the spike is
Health checks and LB monitorsFailover for one predefined failureEvery failure that isn't "origin down"
Audit logsA complete record of changesConnecting 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.

Native automation covers both ends. The middle, where the time goes, is still a person.

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.

cloudflareautomationsecuritycloudflareapiwafdnscdn