Skip to main content

Cloudflare Pages REST API

Learning Focus

By the end of this module you will be able to automate Cloudflare Pages management using the REST API — listing projects, triggering deployments, managing domains, and integrating with external CI/CD systems.

Overview​

The Cloudflare Pages API is part of the Cloudflare API v4 — a RESTful JSON API that lets you automate every action available in the dashboard.

Base URL:

https://api.cloudflare.com/client/v4

Pages-specific base path:

/accounts/{account_id}/pages/projects

Authentication​

All API requests require authentication via either:

Create an API Token with Pages permissions
# Required permissions:
# Account > Cloudflare Pages > Edit
# Zone > Zone > Read (only if managing custom domains)

Include the token in every request:

curl https://api.cloudflare.com/client/v4/accounts/{account_id}/pages/projects \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"

Global API Key (Legacy — Avoid in New Projects)​

curl https://api.cloudflare.com/client/v4/accounts/{account_id}/pages/projects \
-H "X-Auth-Email: your@email.com" \
-H "X-Auth-Key: your-global-api-key" \
-H "Content-Type: application/json"

Setup: Environment Variables​

Set these in your shell to use all code examples below:

Configure environment
export CF_API_TOKEN="your-api-token"
export CF_ACCOUNT_ID="your-account-id"
export CF_PROJECT="my-docs"

Find your Account ID:

  1. Log in to dash.cloudflare.com
  2. Select any domain → right sidebar shows Account ID
  3. Or: wrangler whoami

Projects API​

List All Projects​

GET /pages/projects
curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .

Response:

{
"result": [
{
"id": "abc123",
"name": "my-docs",
"subdomain": "my-docs.pages.dev",
"domains": ["my-docs.pages.dev", "docs.example.com"],
"production_branch": "main",
"created_on": "2024-01-15T10:00:00.000Z",
"latest_deployment": {
"id": "deploy-xyz",
"url": "https://abc123.my-docs.pages.dev",
"environment": "production",
"created_on": "2024-04-22T05:00:00.000Z"
}
}
],
"success": true,
"errors": [],
"messages": []
}

Get a Specific Project​

GET /pages/projects/{project_name}
curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .

Create a New Project​

POST /pages/projects
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "my-docs",
"production_branch": "main"
}' | jq .

Update Project Settings​

PATCH /pages/projects/{project_name}
curl -X PATCH \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"deployment_configs": {
"production": {
"build_config": {
"build_command": "npm run build",
"destination_dir": "build"
},
"env_vars": {
"NODE_VERSION": {
"value": "20"
}
}
}
}
}' | jq .

Delete a Project​

DELETE /pages/projects/{project_name}
curl -X DELETE \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}" \
-H "Authorization: Bearer ${CF_API_TOKEN}"

Deployments API​

List Deployments​

GET /pages/projects/{name}/deployments
curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[] | {id, url, environment, created_on, latest_stage}'

Sample output (jq-filtered):

{
"id": "abc123def456",
"url": "https://abc123def456.my-docs.pages.dev",
"environment": "production",
"created_on": "2024-04-22T05:00:00.000Z",
"latest_stage": {
"name": "deploy",
"status": "success"
}
}

Get a Specific Deployment​

GET /pages/projects/{name}/deployments/{deployment_id}
DEPLOYMENT_ID="abc123def456"

curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments/${DEPLOYMENT_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .

Retry a Failed Deployment​

POST /pages/projects/{name}/deployments/{id}/retry
DEPLOYMENT_ID="abc123def456"

curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments/${DEPLOYMENT_ID}/retry" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .

Rollback to a Deployment​

POST /pages/projects/{name}/deployments/{id}/rollback
# This promotes a previous deployment to production
DEPLOYMENT_ID="previous-stable-deployment-id"

curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments/${DEPLOYMENT_ID}/rollback" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .
Rollback via API

This is one of the most powerful API features — you can programmatically restore a previous production deployment in one API call, with no rebuild required.

Delete a Deployment​

DELETE /pages/projects/{name}/deployments/{id}
DEPLOYMENT_ID="abc123def456"

curl -X DELETE \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments/${DEPLOYMENT_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}"

Get Deployment Build Logs​

GET /pages/projects/{name}/deployments/{id}/history/logs
DEPLOYMENT_ID="abc123def456"

curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments/${DEPLOYMENT_ID}/history/logs" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result.logs[] | "\(.ts) \(.line)"' -r

Custom Domains API​

List Custom Domains​

GET /pages/projects/{name}/domains
curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/domains" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[] | {id, name, status, created_on}'

Response fields:

{
"id": "domain-id-123",
"name": "docs.example.com",
"status": "active",
"created_on": "2024-01-15T10:00:00.000Z"
}

Domain statuses:

StatusMeaning
initializingDNS verification pending
pending_cert_issuanceSSL cert being issued
activeLive and serving traffic
blockedDNS conflict or verification failed

Add a Custom Domain​

POST /pages/projects/{name}/domains
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/domains" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "docs.yourdomain.com"}' | jq .

Get Domain Status​

GET /pages/projects/{name}/domains/{domain}
curl "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/domains/docs.yourdomain.com" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq .

Delete a Custom Domain​

DELETE /pages/projects/{name}/domains/{domain}
curl -X DELETE \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/domains/docs.yourdomain.com" \
-H "Authorization: Bearer ${CF_API_TOKEN}"

Direct Upload via API​

For programmatic deployments without a Git integration, you can upload files directly via the API.

Step 1 — Create an Upload Deployment​

POST — create upload slot
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}/deployments" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"branch": "main"}' | jq .

Response includes upload_token and id for Step 2.

Step 2 — Upload Files (Multipart Form)​

POST — upload file assets
# Typically done via Wrangler (wraps this API):
wrangler pages deploy ./build --project-name my-docs

# Under the hood this uses:
# POST /accounts/{id}/pages/projects/{name}/uploads
# with multipart/form-data containing all build files
note

Direct upload via raw API is complex (requires file manifest hashing). Use wrangler pages deploy instead — it handles the entire upload process reliably.


Practical Automation Scripts​

Script: Get Latest Deployment URL​

get-deploy-url.sh
#!/usr/bin/env bash
# Get the URL of the latest production deployment

LATEST_URL=$(curl -s \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/${CF_PROJECT}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
| jq -r '.result.latest_deployment.url')

echo "Latest deployment: $LATEST_URL"

Script: Monitor Build Status​

wait-for-deploy.sh
#!/usr/bin/env bash
# Poll until the latest deployment completes

PROJECT="${CF_PROJECT}"
MAX_WAIT=300 # 5 minutes
ELAPSED=0

echo "Waiting for deployment to complete..."

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

echo " Status: $STATUS (${ELAPSED}s elapsed)"

if [ "$STATUS" = "success" ]; then
echo "✅ Deployment succeeded!"
exit 0
elif [ "$STATUS" = "failure" ]; then
echo "❌ Deployment failed!"
exit 1
fi

sleep 10
ELAPSED=$((ELAPSED + 10))
done

echo "⏱ Timeout: deployment did not complete in ${MAX_WAIT}s"
exit 1

Script: Purge Cache After Deployment​

purge-cache.sh
#!/usr/bin/env bash
# Purge Cloudflare cache after a Pages deployment

ZONE_ID="your-zone-id"

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" \
-d '{"purge_everything": true}'

Script: API Health Check (All Projects Status)​

check-all-projects.sh
#!/usr/bin/env bash
# List all Pages projects and their last deployment status

curl -s \
"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
| jq -r '.result[] | "\(.name) | \(.latest_deployment.url) | \(.latest_deployment.created_on)"'

API Response Structure​

All Cloudflare API v4 responses follow this structure:

{
"result": { /* payload */ },
"success": true,
"errors": [],
"messages": [],
"result_info": {
"page": 1,
"per_page": 20,
"total_count": 42,
"count": 20
}
}

On error:

{
"result": null,
"success": false,
"errors": [
{
"code": 8000000,
"message": "An unknown error has occurred"
}
],
"messages": []
}

Rate Limits​

PlanRequests per minute
Free1,200
Pro1,200
Business1,200
429 Too Many Requests

If you hit the rate limit, the API returns HTTP 429. Add exponential backoff to your automation scripts.


Key Takeaways​

  • The Cloudflare Pages API base path is /accounts/{account_id}/pages/projects.
  • Authenticate with a scoped API token (Cloudflare Pages: Edit) — not the global API key.
  • Key action verbs: GET (read), POST (create/trigger), PATCH (update), DELETE (remove).
  • Rollback via API is a single POST to /deployments/{id}/rollback — no rebuild required.
  • Get build logs via /deployments/{id}/history/logs for programmatic failure detection.
  • wrangler pages deploy wraps the direct upload API — use the CLI for file uploads.

What's Next​