Cloudflare API
Cloudflare API via curl + jq for deployment status, DNS management, and cache operations. Raw API when netapi’s rich tables aren’t enough.
Token & Identity
Verify token — status, expiry, identity
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/user/tokens/verify" | jq '{
status: .result.status,
expires: (.result.expires_on // "never"),
not_before: (.result.not_before // "immediate"),
issued: .result.issued_on[:10],
id: .result.id[:12]
}'
Health check — silent, exit-code only (for scripts/aliases)
curl -sf -H "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/user/tokens/verify" \
| jq -e '.result.status == "active"' > /dev/null \
&& echo "CF token: OK" || echo "CF token: FAILED"
Policy audit — name, scopes, permissions (requires
API Tokens:Read)TOKEN_ID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/user/tokens/verify" | jq -r '.result.id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/user/tokens/${TOKEN_ID}" | jq '{
name: .result.name,
status: .result.status,
expires: (.result.expires_on // "never"),
policies: [.result.policies[] | {
effect: .effect,
resources: (.resources | keys),
permissions: [.permission_groups[] | .name]
}]
}'
Returns 9109 Unauthorized without API Tokens:Read scope. Add via: CF Dashboard → API Tokens → Edit → User > API Tokens > Read.
|
Scope probe — test which APIs the token can access (no extra permissions needed)
{
verify=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/user/tokens/verify")
declare -A endpoints=(
[tokens]="user/tokens/verify"
[accounts]="accounts"
[pages]="accounts/${CF_ACCOUNT_ID}/pages/projects"
[zones]="zones"
[dns]="zones?name=domusdigitalis.dev"
[workers]="accounts/${CF_ACCOUNT_ID}/workers/scripts"
[r2]="accounts/${CF_ACCOUNT_ID}/r2/buckets"
[token-audit]="user/tokens"
)
scopes=$(for name in "${(k)endpoints[@]}"; do
code=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/${endpoints[$name]}")
printf '"%s":%s,' "$name" "$code"
done)
echo "${verify}" | jq --argjson scopes "{${scopes%,}}" '{
id: .result.id[:12],
status: .result.status,
expires: (.result.expires_on // "never"),
not_before: (.result.not_before // "immediate"),
scopes: ($scopes | to_entries | map({key, value: (if .value == 200 then "allowed" elif .value == 403 then "denied" else "error:\(.value)" end)}) | from_entries)
}'
}
List accounts accessible by token
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts" | jq '[.result[] | {id, name}]'
Pages — Projects
List all Pages projects with domains
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects" \
| jq '[.result[] | {name, subdomain, domains}]'
Get project build configuration
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs" \
| jq '{name: .result.name, build_command: .result.build_config.build_command, output: .result.build_config.destination_dir, branch: .result.production_branch}'
Inspect environment variables (SECURITY: check for plain_text exposure)
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs" \
| jq '{
preview: [.result.deployment_configs.preview.env_vars | to_entries[] | {key, type: .value.type}],
production: [.result.deployment_configs.production.env_vars | to_entries[] | {key, type: .value.type}]
}'
Env vars with "type": "plain_text" are returned in full by the API. Use Encrypt (secret_text) for credentials.
|
Pages — Deployments
One-shot status — is the latest deployment passing or failing right now?
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0] | "[\(.latest_stage.status | ascii_upcase)] \(.latest_stage.name) — \(.id[:12]) — \(.created_on[:19])"'
List last 5 deployments with status and commit
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq '[.result[:5][] | {id: .id[:12], status: .latest_stage.status, stage: .latest_stage.name, created: .created_on[:19], commit: .deployment_trigger.metadata.commit_message[:50]}]'
Stage breakdown — latest deployment (pulls full ID automatically)
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0].id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}" \
| jq '{id: .result.id[:12], status: .result.latest_stage.status, stages: [.result.stages[] | {name, status, started_on: .started_on[:19], ended_on: .ended_on[:19]}]}'
Stage breakdown — Nth deployment (0=latest, 1=previous, etc.)
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[1].id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}" \
| jq '{id: .result.id[:12], status: .result.latest_stage.status, stages: [.result.stages[] | {name, status, started_on: .started_on[:19], ended_on: .ended_on[:19]}]}'
Filter — failures only
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq '[.result[] | select(.latest_stage.status == "failure") | {id: .id[:12], stage: .latest_stage.name, created: .created_on[:19], commit: .deployment_trigger.metadata.commit_message[:60]}] | .[:5]'
Pages — Build Analysis
Compare build times across last 10 deployments
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq '[.result[:10][] | {
id: .id[:12],
status: .latest_stage.status,
build_sec: (if (.stages[] | select(.name == "build") | .ended_on) and (.stages[] | select(.name == "build") | .started_on) then (((.stages[] | select(.name == "build") | .ended_on[:19] + "Z") | fromdateiso8601) - ((.stages[] | select(.name == "build") | .started_on[:19] + "Z") | fromdateiso8601)) else null end),
commit: .deployment_trigger.metadata.commit_message[:60]
}]'
Per-stage timing with duration (diagnose timeouts)
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq '[.result[:5][] | {id: .id[:12], status: .latest_stage.status, stages: [.stages[] | {name, status, duration: (if .ended_on and .started_on then (((.ended_on[:19] + "Z") | fromdateiso8601) - ((.started_on[:19] + "Z") | fromdateiso8601) | tostring + "s") else "n/a" end)}]}]'
Build duration of latest successful deploy
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '[.result[] | select(.latest_stage.status == "success")] | .[0] | .stages[] | select(.name == "build") | "\(.started_on[:19]) → \(.ended_on[:19])"'
Success/failure ratio — last 20 deployments
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments?per_page=20" \
| jq '{
total: (.result | length),
success: ([.result[] | select(.latest_stage.status == "success")] | length),
failure: ([.result[] | select(.latest_stage.status == "failure")] | length),
latest: .result[0] | {status: .latest_stage.status, stage: .latest_stage.name, commit: .deployment_trigger.metadata.commit_message | split("\n")[0][:60]}
}'
Observation: domus-scripture (~6,800 pages) adds ~15 min to Cloudflare build
Without scripture: 3 min 39s → success With scripture: 18 min 12s → failure (Cloudflare 20-min timeout) Local hub build: 2 min 15s → Cloudflare is ~8x slower
Pages — Build Logs
Stream full build log for latest deployment
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0].id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}/history/logs" \
| jq -r '.result.data[] | "\(.ts[:19]) \(.line)"'
Tail — last 10 lines of build log
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0].id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}/history/logs" \
| jq -r '.result.data[-10:][] | "\(.ts[:19]) \(.line)"'
Deployment dashboard URL — open in browser
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0].id')
printf "https://dash.cloudflare.com/${CF_ACCOUNT_ID}/pages/view/domus-docs/%s\n" "${DID}"
Pages — Actions
Retry a failed deployment
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0].id')
curl -sX POST -H "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}/retry" \
| jq '{id: .result.id[:12], status: .result.latest_stage.status}'
Rollback to a specific deployment
DID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[2].id')
curl -sX POST -H "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments/${DID}/rollback" \
| jq '{id: .result.id[:12], status: .result.latest_stage.status}'
Pages — Monitoring
Watch deployment — poll every 30s until complete
while true; do
result=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
| jq -r '.result[0] | "\(.latest_stage.name):\(.latest_stage.status) | \(.id[:12]) | \(.deployment_trigger.metadata.commit_message | split("\n")[0][:50])"')
printf "%s %s\n" "$(date +%H:%M:%S)" "${result}"
[[ "${result}" == *":success"* || "${result}" == *":failure"* ]] && break
sleep 30
done
Zones & DNS
List all zones
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones" \
| jq '[.result[] | {name, id: .id[:12], status, plan: .plan.name}]'
Get zone ID for a domain
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=domusdigitalis.dev" | jq '.result[0].id'
List DNS records for a zone
ZONE_ID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=domusdigitalis.dev" | jq -r '.result[0].id')
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
| jq '[.result[] | {type, name, content, proxied, ttl}]'
Cache
Purge entire cache for a zone
ZONE_ID=$(curl -sH "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=domusdigitalis.dev" | jq -r '.result[0].id')
curl -sX POST -H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"purge_everything": true}' \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" | jq '.success'
netapi Equivalents
netapi wraps these API calls — use when the rich table output is enough
netapi cloudflare pages list
netapi cloudflare pages get domus-docs
netapi cloudflare pages deployments domus-docs
netapi cloudflare zones
netapi cloudflare zone domusdigitalis.dev
netapi cloudflare purge domusdigitalis.dev
netapi cloudflare dns list domusdigitalis.dev
When you need raw JSON for jq pipelines, scripting, or debugging failures — use curl directly against the API.
See Also
-
curl Patterns — request construction and auth
-
REST — HTTP methods and status codes