Cloudflare Commands

Overview

The netapi cloudflare command group provides CLI access to Cloudflare API for:

  • Zone management

  • Cache purging

  • DNS record management

  • Pages project deployments

  • Access (Zero Trust) applications and policies

Prerequisites

Load secrets before using Cloudflare commands:

dsource d000 dev/app

Required environment variables:

Variable Description

CF_API_TOKEN

Cloudflare API token with appropriate permissions

CF_ACCOUNT_ID

Cloudflare account ID (auto-detected from zones if not set)

API Token Permissions

Create a token at dash.cloudflare.com/profile/api-tokens with:

Permission Required For

Zone:Zone:Read

zones, zone, cache operations, DNS lookups

Zone:Cache Purge:Edit

purge command

Zone:DNS:Edit

dns commands

Account:Cloudflare Pages:Read

pages list, pages get, pages deployments

Account:Cloudflare Pages:Edit

pages create, pages update, pages delete, pages domain

Account:Access: Apps and Policies:Edit

access commands

For security, use separate tokens with minimal permissions:

Token Name Permissions Use Case

CF_API_TOKEN (local)

Pages:Read, Access:Edit, Cache Purge, Zone:Read

Local CLI operations, IP-filtered to your machine

CF_DNS_TOKEN (local)

Zone:Zone:Read, Zone:DNS:Edit

DNS management, IP-filtered

ci-pages-deploy-only (CI)

Account:Cloudflare Pages:Edit

GitHub Actions / GitLab CI deployments, no IP filter

Zone Commands

zones

List all Cloudflare zones in your account:

netapi cloudflare zones

zone

Get details for a specific zone:

netapi cloudflare zone domusdigitalis.dev

Cache Commands

purge

Purge Cloudflare cache for a domain. Essential after deploying new assets.

# Purge all cache (with confirmation)
netapi cloudflare purge domusdigitalis.dev

# Skip confirmation
netapi cloudflare purge domusdigitalis.dev -y

# Purge specific URL
netapi cloudflare purge domusdigitalis.dev --url https://docs.domusdigitalis.dev/_/css/site.css

Options:

Option Required Description

domain

Yes

Domain name (e.g., domusdigitalis.dev)

-u, --url

No

Specific URL to purge (purges all if not specified)

-y, --yes

No

Skip confirmation prompt

DNS Commands

Manage DNS records for your zones.

dns list

List DNS records for a domain:

# List all records
netapi cloudflare dns list domusdigitalis.dev

# Filter by record type
netapi cloudflare dns list domusdigitalis.dev -t CNAME
netapi cloudflare dns list domusdigitalis.dev -t A
netapi cloudflare dns list domusdigitalis.dev -t TXT

dns add

Add a DNS record:

# Add A record
netapi cloudflare dns add domusdigitalis.dev -t A -n test -c 10.50.1.100

# Add CNAME record with proxy enabled
netapi cloudflare dns add domusdigitalis.dev -t CNAME -n www -c domusdigitalis.dev -p

# Add TXT record
netapi cloudflare dns add domusdigitalis.dev -t TXT -n _dmarc -c "v=DMARC1; p=none"

# Add MX record with priority
netapi cloudflare dns add domusdigitalis.dev -t MX -n @ -c mail.example.com --priority 10

Options:

Option Required Description

domain

Yes

Domain name

-t, --type

Yes

Record type (A, AAAA, CNAME, TXT, MX)

-n, --name

Yes

Record name (e.g., www, @, subdomain)

-c, --content

Yes

Record content (IP, hostname, text)

--ttl

No

TTL in seconds (1 = auto, default)

-p, --proxied

No

Enable Cloudflare proxy

--priority

No

MX record priority

dns delete

Delete a DNS record:

# Delete with confirmation
netapi cloudflare dns delete domusdigitalis.dev --id abc123def456

# Skip confirmation
netapi cloudflare dns delete domusdigitalis.dev --id abc123def456 -y

Pages Commands

Manage Cloudflare Pages projects and deployments. Supports full project lifecycle: create, configure, deploy, and add custom domains.

pages list

List all Cloudflare Pages projects:

netapi cloudflare pages list
Example Output
                              Cloudflare Pages Projects
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name               ┃ Subdomain                  ┃ Domains                                       ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ domus-docs         │ domus-docs.pages.dev       │ domus-docs.pages.dev, docs.domusdigitalis.dev │
│ architectus-docs   │ architectus-docs.pages.dev │ architectus-docs.pages.dev, docs.architectus.dev │
└────────────────────┴────────────────────────────┴───────────────────────────────────────────────┘

pages get

Get details for a specific Pages project:

netapi cloudflare pages get architectus-docs
Example Output
Project: architectus-docs
Subdomain: architectus-docs.pages.dev
Created: 2026-02-17T23:38:42

Build Config:
  Command: N/A
  Output:  build/site
  Root:    /

Domains:
  - architectus-docs.pages.dev
  - docs.architectus.dev

pages create

Create a new Pages project (direct upload, no git integration):

# Basic project
netapi cloudflare pages create my-project --output "dist"

# Antora documentation site
netapi cloudflare pages create architectus-docs --output "build/site"

# With build command (for CI reference)
netapi cloudflare pages create my-site \
    --build "npm ci && npm run build" \
    --output "build/site" \
    --branch main

Options:

Option Required Description

name

Yes

Project name (becomes subdomain)

-o, --output

No

Build output directory

-b, --build

No

Build command (reference only for direct upload)

-r, --root

No

Root directory (default: /)

--branch

No

Production branch (default: main)

pages create creates a direct upload project. Cloudflare does not build the site - your CI (GitHub Actions, GitLab CI) builds and uploads artifacts via wrangler pages deploy.

For git-connected projects (Cloudflare builds from repo), use the dashboard or Terraform.

pages update

Update a Pages project configuration:

# Update output directory
netapi cloudflare pages update architectus-docs --output "build/site"

# Update build command
netapi cloudflare pages update my-site --build "npm run build:prod"

# Update multiple settings
netapi cloudflare pages update my-site \
    --build "npm ci && npm run build" \
    --output "dist" \
    --root "/packages/web"

pages delete

Delete a Pages project:

# With confirmation prompt
netapi cloudflare pages delete my-old-project

# Skip confirmation
netapi cloudflare pages delete my-old-project -y

This permanently deletes the project and all deployments. Custom domains will stop working immediately.

pages domain

Add a custom domain to a Pages project:

netapi cloudflare pages domain architectus-docs docs.architectus.dev
Example Output
Added domain: docs.architectus.dev
Project: architectus-docs
Status: initializing

Note: Configure DNS CNAME record pointing to:
  architectus-docs.pages.dev

After adding the domain, create the DNS record:

# If using separate DNS token
CF_API_TOKEN=$CF_DNS_TOKEN netapi cloudflare dns add architectus.dev \
    -t CNAME -n docs -c architectus-docs.pages.dev --proxied

pages deployments

List recent deployments for a Pages project:

# Show last 5 deployments (default)
netapi cloudflare pages deployments domus-docs

# Show last 10 deployments
netapi cloudflare pages deployments domus-docs -n 10

# JSON output for scripting
netapi cloudflare pages deployments domus-docs --json
Example Output
                                  Deployments: domus-docs
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ ID           ┃ Status    ┃ Environment ┃ Branch ┃ URL                          ┃ Created             ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│ ff16df47-aa6 │ success   │ production  │ main   │ https://ff16df47.domus-docs… │ 2026-02-22T00:04:02 │
│ c64c5822-056 │ active    │ production  │ main   │ https://c64c5822.domus-docs… │ 2026-02-22T00:03:56 │
│ d3febab0-78c │ failure   │ production  │ main   │ https://d3febab0.domus-docs… │ 2026-02-21T23:36:16 │
└──────────────┴───────────┴─────────────┴────────┴──────────────────────────────┴─────────────────────┘

Deployment Status Values

Status Stage Description

success

deploy

Deployment completed successfully. Site is live.

active

queued/initialize/build/deploy

Deployment is currently in progress.

failure

varies

Deployment failed. Check logs for details.

idle

queued

Deployment queued but not yet started.

canceled

varies

Deployment was manually canceled.

Monitor Deployment Progress

Watch deployment status in real-time:

# Watch latest deployment (updates every 5s)
watch -n 5 'netapi cloudflare pages deployments domus-docs -n 1'

# Or with jq for cleaner output
watch -n 5 'curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq -r ".result[0] | \"\\(.id[:12]) \\(.latest_stage.status) \\(.latest_stage.name)\""'

pages deployment (single)

Get details for a specific deployment:

netapi cloudflare pages deployment domus-docs 8f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
Example Output
{
  "id": "8f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "project_name": "domus-docs",
  "environment": "production",
  "url": "https://8f3a2b1c.domus-docs.pages.dev",
  "latest_stage": {
    "name": "deploy",
    "status": "success",
    "started_on": "2026-02-13T07:29:45Z",
    "ended_on": "2026-02-13T07:30:00Z"
  },
  "deployment_trigger": {
    "type": "wrangler",
    "metadata": {
      "commit_hash": "7a235e0"
    }
  },
  "stages": [
    {"name": "queued", "status": "success"},
    {"name": "initialize", "status": "success"},
    {"name": "clone_repo", "status": "skipped"},
    {"name": "build", "status": "skipped"},
    {"name": "deploy", "status": "success"}
  ]
}

Checking Deployment Status (CI/CD)

Poll for deployment completion in automation scripts:

#!/bin/bash
# wait-for-deployment.sh - Wait for Cloudflare Pages deployment to complete

PROJECT="domus-docs"
MAX_WAIT=300  # 5 minutes
INTERVAL=10   # Check every 10 seconds

# Get latest deployment ID
DEPLOYMENT_ID=$(netapi cloudflare pages deployments "$PROJECT" -n 1 --json | jq -r '.[0].id')

echo "Waiting for deployment $DEPLOYMENT_ID..."

elapsed=0
while [ $elapsed -lt $MAX_WAIT ]; do
  STATUS=$(netapi cloudflare pages deployment "$PROJECT" "$DEPLOYMENT_ID" --json | jq -r '.latest_stage.status')

  case $STATUS in
    success)
      echo "Deployment successful!"
      exit 0
      ;;
    failure)
      echo "Deployment failed!"
      exit 1
      ;;
    active|idle)
      echo "Status: $STATUS (waiting...)"
      sleep $INTERVAL
      elapsed=$((elapsed + INTERVAL))
      ;;
    *)
      echo "Unknown status: $STATUS"
      exit 2
      ;;
  esac
done

echo "Timeout waiting for deployment"
exit 3

Direct API Commands (curl)

When netapi doesn’t support a feature, use the Cloudflare API directly.

List Deployments with Full Details

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=5" \
  | jq '.result[] | {id, status: .latest_stage.status, stage: .latest_stage.name, created: .created_on}'

Get Single Deployment Status

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments/<deployment-id>" \
  | jq '{status: .result.latest_stage.status, stage: .result.latest_stage.name}'

Get Build Logs for Failed Deployment

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments/<deployment-id>/history/logs" \
  | jq -r '.result.data[].line' | tail -50

Quick Status Check (Latest Deployment)

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq '.result[0] | {status: .latest_stage.status, stage: .latest_stage.name}'

Verify API Token Has Pages Permission

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects" \
  | jq '.success'
# Returns: true if token has Pages:Read permission

Troubleshooting Deployment Failures

Check deployment logs when status is failure:

# Get deployment details with logs
netapi cloudflare pages deployment domus-docs <deployment-id> --logs
Table 1. Common Failure Causes
Error Solution

Build failed

Check build command in Pages settings. Verify local build works.

Deploy failed: asset too large

Individual files must be < 25MB. Split or compress assets.

Rate limited

Too many deployments. Wait 1 minute between deploys.

Authentication failed

Verify CF_API_TOKEN has Pages:Edit permission.

Wrangler Pages Deployment

For direct deployments to Cloudflare Pages (bypassing the dashboard), use wrangler:

List Projects

wrangler pages project list
Example Output
┌──────────────┬───────────────────────────────────────────────┬──────────────┬────────────────┐
│ Project Name │ Project Domains                               │ Git Provider │ Last Modified  │
├──────────────┼───────────────────────────────────────────────┼──────────────┼────────────────┤
│ domus-docs   │ domus-docs.pages.dev, docs.domusdigitalis.dev │ Yes          │ 5 minutes ago  │
├──────────────┼───────────────────────────────────────────────┼──────────────┼────────────────┤
│ domus-ui     │ domus-ui.pages.dev, ui.domusdigitalis.dev     │ Yes          │ 10 minutes ago │
└──────────────┴───────────────────────────────────────────────┴──────────────┴────────────────┘

Deploy to Pages

Deploy a local directory to Cloudflare Pages:

# Deploy Antora site
wrangler pages deploy build/site --project-name=domus-docs

# Deploy Antora UI bundle
wrangler pages deploy build --project-name=domus-ui
Example Output
⛅️ wrangler 4.64.0
───────────────────
Uploading... (794/794)
✨ Success! Uploaded 567 files (227 already uploaded) (4.29 sec)

🌎 Deploying...
✨ Deployment complete! Take a peek over at https://a5568b3d.domus-docs.pages.dev

The preview URL (e.g., a5568b3d.domus-docs.pages.dev) is for the specific deployment. The production URL (e.g., docs.domusdigitalis.dev) updates automatically.

Full Deploy Workflow

Build and deploy without touching the dashboard:

# 1. Build the Antora site
cd ~/atelier/_bibliotheca/domus-docs
make local

# 2. Deploy to Pages
wrangler pages deploy build/site --project-name=domus-docs

# 3. Purge CDN cache (optional but recommended)
netapi cloudflare purge domusdigitalis.dev -y

For the Antora UI bundle:

# 1. Build the UI bundle
cd ~/atelier/_bibliotheca/domus-antora-ui
npx gulp bundle

# 2. Deploy to Pages
wrangler pages deploy build --project-name=domus-ui

Access (Zero Trust) Commands

Manage Cloudflare Access applications and policies via CLI.

access apps

List Access applications:

netapi cloudflare access apps
Example Output
┌────────────────────────────────────────────────────────────────────────────────────┐
│ Cloudflare Access Applications                                                      │
├──────────────────────┬───────────────────────────────────┬─────────────┬───────────┤
│ Name                 │ Domain                            │ Type        │ ID        │
├──────────────────────┼───────────────────────────────────┼─────────────┼───────────┤
│ domus-docs           │ docs.domusdigitalis.dev           │ self_hosted │ 328ba6e8… │
│ domus-docs-static    │ docs.domusdigitalis.dev/_/*       │ self_hosted │ ac2893db… │
│ antora-ui-bundle     │ ui.domusdigitalis.dev             │ self_hosted │ e9c4b01d… │
└──────────────────────┴───────────────────────────────────┴─────────────┴───────────┘

access policies

List policies for an Access application:

netapi cloudflare access policies 328ba6e8-bf0d-4490-8f66-8837acaaaa82

access bypass-static

One-command solution for Antora sites behind Access. Creates a bypass for static assets (/_/*):

netapi cloudflare access bypass-static docs.domusdigitalis.dev

This command:

  1. Creates a new Access application for docs.domusdigitalis.dev/_/*

  2. Adds a bypass policy allowing everyone

  3. Static assets (CSS, JS, fonts) now load without authentication

Example
netapi cloudflare access bypass-static docs.domusdigitalis.dev

# Output:
# Creating Access app: docs-static
# Domain pattern: docs.domusdigitalis.dev/_/*
# Created app: ac2893db-f5c2-40dd-b2e1-08d1a1f047c2
# Created bypass policy: fae81e60-b364-4aaa-b6bc-197e660e5b6e
# Success! Static assets will now load without authentication.
# Verify with: curl -sI https://docs.domusdigitalis.dev/_/css/site.css | head -1

Options:

Option Required Description

domain

Yes

Domain to bypass (e.g., docs.example.com)

-p, --path

No

Path pattern (default: /_/*)

-n, --name

No

Application name (default: <subdomain>-static)

access create-app

Create an Access application:

netapi cloudflare access create-app -n "My App" -d app.domusdigitalis.dev

# With custom session duration
netapi cloudflare access create-app -n "My App" -d app.domusdigitalis.dev -s 12h

access add-policy

Add a policy to an Access application:

# Allow specific email
netapi cloudflare access add-policy <app-id> -n "Allow Me" -d allow -e myemail@example.com

# Bypass for everyone
netapi cloudflare access add-policy <app-id> -n "Bypass All" -d bypass --everyone

access delete-app

Delete an Access application:

netapi cloudflare access delete-app ac2893db-f5c2-40dd-b2e1-08d1a1f047c2

Common Workflows

Deploy and Purge Cache

After deploying a new Antora site build:

# 1. Trigger Pages deployment (via git push)
git push

# 2. Wait for deployment to complete
netapi cloudflare pages deployments domus-docs -n 1

# 3. Purge CDN cache
netapi cloudflare purge domusdigitalis.dev -y

Fix Antora Site Behind Access

If your Antora site shows a white page because CSS/JS returns 302 redirects:

# 1. Verify the issue
curl -sI "https://docs.domusdigitalis.dev/_/css/site.css" | head -1
# If HTTP/2 302 -> Access is blocking

# 2. Create static assets bypass
netapi cloudflare access bypass-static docs.domusdigitalis.dev

# 3. Verify fix
curl -sI "https://docs.domusdigitalis.dev/_/css/site.css" | head -1
# Should show HTTP/2 200

# 4. Clear browser cache (Ctrl+Shift+R)

Add DNS Record for New Subdomain

# Add CNAME pointing to Pages
netapi cloudflare dns add domusdigitalis.dev -t CNAME -n newsite -c domus-docs.pages.dev -p

# Verify
netapi cloudflare dns list domusdigitalis.dev -t CNAME

Validation

Quick validation of all Cloudflare commands:

# Zone commands
netapi cloudflare zones
netapi cloudflare zone domusdigitalis.dev

# DNS commands
netapi cloudflare dns list domusdigitalis.dev

# Pages commands
netapi cloudflare pages list
netapi cloudflare pages deployments domus-docs

# Access commands
netapi cloudflare access apps

jq Patterns for Cloudflare API

Advanced jq patterns for parsing Cloudflare API responses. All examples assume credentials loaded:

eval "$(dsource d000 dev/app)"

Deployment Status Queries

Latest Deployment Status (One-liner)

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq -r '.result[0] | "\(.id[:12]) \(.latest_stage.status) \(.latest_stage.name)"'
Output
ff16df47-aa6 success deploy

Deployment with Timing Info

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq '.result[0] | {
      id: .id[:12],
      status: .latest_stage.status,
      stage: .latest_stage.name,
      started: .latest_stage.started_on,
      ended: .latest_stage.ended_on,
      duration: (if .latest_stage.ended_on and .latest_stage.started_on then
        (((.latest_stage.ended_on | fromdateiso8601) - (.latest_stage.started_on | fromdateiso8601)) | tostring) + "s"
      else "in progress" end)
    }'
Output
{
  "id": "ff16df47-aa6",
  "status": "success",
  "stage": "deploy",
  "started": "2026-02-22T00:03:45Z",
  "ended": "2026-02-22T00:04:02Z",
  "duration": "17s"
}

All Stages with Status

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq -r '.result[0].stages[] | "\(.name): \(.status)"'
Output
queued: success
initialize: success
clone_repo: skipped
build: skipped
deploy: success

Find Failed Deployments

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=20" \
  | jq -r '.result[] | select(.latest_stage.status == "failure") | "\(.id[:12]) \(.created_on) \(.latest_stage.name)"'

Deployments in Last 24 Hours

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=50" \
  | jq --arg since "$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)" \
    '[.result[] | select(.created_on > $since)] | length as $count |
     {total: $count, success: [.[] | select(.latest_stage.status == "success")] | length,
      failure: [.[] | select(.latest_stage.status == "failure")] | length}'
Output
{
  "total": 8,
  "success": 7,
  "failure": 1
}

DNS Record Queries

List Records as Table

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
  | jq -r '.result[] | [.type, .name, .content[:40], (if .proxied then "proxied" else "dns-only" end)] | @tsv' \
  | column -t

Find Records by Type

# All CNAME records
curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?type=CNAME" \
  | jq -r '.result[] | "\(.name) -> \(.content)"'

Records Not Proxied (DNS-only)

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
  | jq -r '.result[] | select(.proxied == false) | "\(.type) \(.name)"'

Access Application Queries

List Apps with Policy Count

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps" \
  | jq -r '.result[] | "\(.name) (\(.policies | length) policies) - \(.domain)"'

Find Bypass Policies

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps" \
  | jq -r '.result[] | select(.policies[]?.decision == "bypass") | .name'

Zone Analytics

Requests by Status Code (Last 24h)

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/analytics/dashboard?since=-1440" \
  | jq '.result.totals.requests.http_status | to_entries | sort_by(-.value) | .[:10] | .[] | "\(.key): \(.value)"'

Cache Hit Ratio

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/analytics/dashboard?since=-1440" \
  | jq '.result.totals | {
      total: .requests.all,
      cached: .requests.cached,
      uncached: .requests.uncached,
      hit_ratio: ((.requests.cached / .requests.all * 100) | floor | tostring + "%")
    }'

Utility Patterns

Get Zone ID by Domain Name

CF_ZONE_ID=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones?name=domusdigitalis.dev" \
  | jq -r '.result[0].id')

Get Account ID from Token

CF_ACCOUNT_ID=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts" \
  | jq -r '.result[0].id')

Verify Token Permissions

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/user/tokens/verify" \
  | jq '{valid: .success, expires: .result.expires_on, not_before: .result.not_before}'

Pretty Print with Colors

# Force colors even when piping
curl -s ... | jq -C '.' | less -R

# Compact output for logs
curl -s ... | jq -c '.result[0]'

# Raw strings (no quotes)
curl -s ... | jq -r '.result[0].id'

Conditional Logic

Exit Code Based on Status

# Returns exit code 0 if success, 1 otherwise
curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=1" \
  | jq -e '.result[0].latest_stage.status == "success"' > /dev/null

Wait for Deployment (Script)

#!/bin/bash
# wait-deploy.sh - Wait for Cloudflare Pages deployment

PROJECT="${1:-domus-docs}"
TIMEOUT="${2:-300}"
INTERVAL=10

echo "Waiting for $PROJECT deployment..."

elapsed=0
while [ $elapsed -lt $TIMEOUT ]; do
  STATUS=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/$PROJECT/deployments?per_page=1" \
    | jq -r '.result[0].latest_stage.status')

  case $STATUS in
    success) echo "✓ Deployment successful!"; exit 0 ;;
    failure) echo "✗ Deployment failed!"; exit 1 ;;
    *) printf "\r[%3ds] Status: %-10s" $elapsed "$STATUS" ;;
  esac

  sleep $INTERVAL
  elapsed=$((elapsed + INTERVAL))
done

echo "✗ Timeout after ${TIMEOUT}s"
exit 2

Deployment Status with Emoji

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=5" \
  | jq -r '.result[] | "\(if .latest_stage.status == "success" then "✓" elif .latest_stage.status == "failure" then "✗" else "◐" end) \(.id[:12]) \(.latest_stage.status) \(.created_on[:16])"'
Output
✓ ff16df47-aa6 success 2026-02-22T00:04
✓ c64c5822-056 success 2026-02-22T00:03
✗ d3febab0-78c failure 2026-02-21T23:36
✓ 2e7b1330-a8f success 2026-02-21T21:40
✓ 8424dae3-46e success 2026-02-21T21:14

Transform to Other Formats

CSV Export

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=20" \
  | jq -r '["id","status","stage","created"], (.result[] | [.id[:12], .latest_stage.status, .latest_stage.name, .created_on]) | @csv'

Markdown Table

curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/domus-docs/deployments?per_page=5" \
  | jq -r '["| ID | Status | Stage | Created |", "|---|---|---|---|"], (.result[] | "| \(.id[:12]) | \(.latest_stage.status) | \(.latest_stage.name) | \(.created_on[:16]) |") | .[]'
  • Cloudflare Access + Antora Runbook (infra-ops)

  • Cloudflare Pages Deployment (infra-ops)