Use this skill for service operations only. DO NOT use this skill for CLI implementation lifecycle work such as creating, testing, updating, troubleshooting, validating, removing, or documenting the CLI tool itself; delegate those tasks to cli-tool-expert. Execute cloudflare operations using the `cloudflare` CLI tool. CLI interface for Cloudflare API — manage zones, DNS records, cache, IP access rules, zone traffic analytics, and Workers scripts. Triggers: cloudflare, cloudflare cli, cloudflare dns, cloudflare zones, cloudflare cache, cloudflare access rules, manage dns records, purge cloudflare cache, cloudflare ip rules, block ip cloudflare, cloudflare analytics, zone traffic, page views, top paths, cloudflare workers, worker scripts, upload worker
Resources
1Install
npx skillscat add adbertram/cli-tools/cloudflare-cli Install via the SkillsCat registry.
| Task | Command |
|---|---|
| List zones | cloudflare zones list --table |
| Get zone details | cloudflare zones get ZONE_ID |
| List DNS records | cloudflare dns records list ZONE_ID --table |
| Create DNS record | cloudflare dns records create ZONE_ID --type A --name sub.example.com --content 1.2.3.4 |
| Purge cache | cloudflare cache purge ZONE_ID |
| List access rules | cloudflare access-rules list ZONE_ID --table |
| Block an IP | cloudflare access-rules create ZONE_ID --target ip --value 1.2.3.4 --mode block |
| Set Under Attack mode | cloudflare zones update ZONE_ID --security-level under_attack |
| Traffic totals for a date range | cloudflare analytics summary example.com --start 2026-06-01 --end 2026-06-30 |
| Top pages by HTML page views | cloudflare analytics top-paths example.com --limit 5 --table |
| List Workers scripts | cloudflare workers list |
| Download a Worker script | cloudflare workers get my-worker > worker.js |
| Upload a Worker script | cloudflare workers upload my-worker --file ./worker.js |
| Delete a Worker script | cloudflare workers delete my-worker --force |
| List all queues | cloudflare queues list --limit 0 |
| Get queue details | cloudflare queues get QUEUE_ID ACCOUNT_ID |
| Create a queue | cloudflare queues create issue-manager ACCOUNT_ID |
| </quick_start> |
cloudflare zones list --table
cloudflare dns records list <zone_id> --limit 200Every write fails with exit 1 and a 403:
cloudflare dns records delete <zone_id> <record_id> --force
Error: API request failed (403): Authentication errorThe failure hits every record and every zone identically.
Cause. The stored credential is a scoped Cloudflare User API Token, not
a Global API Key; the CLI authenticates with Authorization: Bearer <token>
using the API token stored in the CLI-tools secret manager as cloudflare-api-key. That token carries only Read permission groups. Cloudflare returns
HTTP 403 with the generic message Authentication error for an out-of-scope
token, which reads like a broken or expired credential and sends diagnosis down
the wrong path.
This is not a header-shape bug. CloudflareClient._update_headers buildsAuthorization: Bearer <token> once in __init__, and _make_request sends
that same header for every HTTP method. Reads and writes are authenticated
identically. Do not go looking for an X-Auth-Key/X-Auth-Email branch or a
per-method code path; there is none, andcloudflare/tests/test_write_path_auth.py locks that in.
cloudflare auth test cannot detect this. Its handler issuesGET /zones, so a read-only token returns authenticated: true andapi_test: passed while every write still fails. Never treat a greenauth test or auth status as proof that writes will work.
How to confirm the credential is a token, not a Global API Key. A Global API
Key is 37 hex characters. An API token is longer, mixed-case, and starts withcfut. auth status shows the masked prefix. GET /user/tokens/verify with theAuthorization: Bearer header returns 200 and"This API Token is valid and active" for a token; a Global API Key fails that
endpoint under Bearer. A valid verify plus a 403 write proves scope, not
validity.
Fix (Adam must mint the token; an agent cannot). Create a replacement API
token in the Cloudflare dashboard at
https://dash.cloudflare.com/profile/api-tokens with Edit permission for each
area the CLI must write, then store it in the secret manager:
| CLI command group | Permission group required |
|---|---|
dns records create/update/delete |
Zone > DNS > Edit |
access-rules create/update/delete |
Zone > Firewall Services > Edit |
cache purge |
Zone > Cache Purge > Purge |
zones update (security level) |
Zone > Zone Settings > Edit |
zones list/get |
Zone > Zone > Read |
analytics summary/top-paths |
Zone > Analytics > Read |
workers list/get |
Account > Workers Scripts > Read |
workers upload/delete |
Account > Workers Scripts > Edit |
Set Zone Resources to include every zone the CLI manages, then rotate:
<cli-tools-root>/_repo/_secret-manager/secrets.sh set cloudflare-api-keyVerify the new scope with a real write against a low-risk zone
(atademos.com, 1bb82acebb2c9cc1e8c334e599db915d) — create a throwaway TXT
record, confirm it, then delete it. Never test writes againstactionblogger.com, adamtheautomator.com, or brickbuddy.io.
CLI behavior since the fix. _make_request now routes any 403 throughbuild_forbidden_error, so the CLI names the refused method and endpoint, the
exact permission group required, the fact that working reads prove missing Edit
scope rather than an expired token, the auth test false-green warning, and thesecrets.sh set rotation command. A bareAPI request failed (403): Authentication error from this CLI now means the
error path itself regressed.
To change or rotate the API key, update the secret directly (never edit the profile.env or paste the key into a prompt):
<cli-tools-root>/_repo/_secret-manager/secrets.sh set cloudflare-api-keyIf auth login reports Missing secret 'cloudflare-api-key', the referenced
secret is absent from the secret manager. Set it with the command above; do not
attempt to re-enter the key through auth login.
This pattern applies to any api_key/PAT/username_password CLI whose sensitive fields
are stored as secret://<name> placeholders: rotate the value withsecrets.sh set <secret-name>, not through interactive auth login.
</known_issues>