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