โ† Cloudflare Edge Architect

Setup guide

Everything you must provision before this skill can run โ€” the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.

Some setupโ‰ˆ 25-40 mincloudflare

Provision a Cloudflare account with Workers Paid and R2 billing enabled, add the target domain as a zone with proxied DNS, then mint one scoped API token carrying the Workers/KV/R2/D1/Queues account permissions plus Zone Settings, Zone WAF and Cache Purge on that zone, and export it (with the account and zone IDs) so wrangler and the raw REST calls both authenticate.

You need to already have

  • A Cloudflare account where you hold the Super Administrator - All Privileges role (only Super Admins can create API tokens covering Workers, R2, D1 and WAF permission groups)
  • Workers Paid plan enabled on the account (~$5/mo) - Durable Objects, Queues, Cache Reserve, Logpush and the 1000-subrequest limit are not available on Free
  • R2 subscription activated once from the dashboard (R2 requires a payment method on file even inside the free tier) before any bucket can be created
  • The target domain already added as a zone with nameservers delegated to Cloudflare and the hostnames in scope set to Proxied (orange cloud), otherwise Worker routes, Cache Rules and WAF rules never see traffic
  • Node.js 18+ locally so `npx wrangler@4` can run; a browser session is needed once for `wrangler login` unless you use CLOUDFLARE_API_TOKEN
  • For an R2 migration only: read credentials on the source bucket (for S3, an IAM user or role with s3:GetObject and s3:ListBucket on the source) plus a separate R2 S3-compatible access key pair

How the pieces connect

Credentials this skill needs

Set these as environment variables. Never paste secrets into a chat or commit them.

VariableWhat it isWhere to get it
CLOUDFLARE_API_TOKEN
secret
Cloudflare scoped API token (wrangler)
format: 40-character opaque string, alphanumeric with - and _ (shown exactly once at creation)
Cloudflare dashboard > profile icon (top right) > My Profile > API Tokens > Create Token > Create Custom Token, or go straight to https://dash.cloudflare.com/profile/api-tokens. Never use the Global API Key - the skill refuses it.
CF_API_TOKEN
secret
Same scoped token, under the name the skill's curl snippets read
format: 40-character opaque string (normally `export CF_API_TOKEN="$CLOUDFLARE_API_TOKEN"`)
No separate creation step: the rules.sh / cache curl blocks in this skill send `Authorization: Bearer $CF_API_TOKEN`, while wrangler reads CLOUDFLARE_API_TOKEN. Export the one token under both names, or mint a second zone-only token if you want the raw REST calls to hold fewer permissions.
CLOUDFLARE_ACCOUNT_ID
Cloudflare account ID
format: 32-character lowercase hex
Cloudflare dashboard > select any domain > Overview > right-hand API section > Account ID, or read it from `npx wrangler whoami`. Required non-interactively whenever the token can see more than one account.
ZONE_ID
Zone ID of the domain being fronted
format: 32-character lowercase hex
Cloudflare dashboard > the domain > Overview > right-hand API section > Zone ID. Every Cache Rules, rate-limiting, WAF and tiered-cache call in this skill interpolates it into /client/v4/zones/$ZONE_ID/...
R2_ACCESS_KEY_ID
secretoptional
R2 S3-compatible access key ID (destination)
format: 32-character hex
Cloudflare dashboard > R2 Object Storage > API > Manage API tokens > Create API token (Object Read & Write). Needed only for `wrangler r2 bucket sippy enable` and for rclone against the R2 S3 endpoint.
R2_SECRET_ACCESS_KEY
secretoptional
R2 S3-compatible secret access key (destination)
format: 64-character hex, displayed once at creation
Same screen as R2_ACCESS_KEY_ID - Cloudflare dashboard > R2 Object Storage > API > Manage API tokens; copy the secret before closing the dialog, it is not retrievable later.
AWS_ACCESS_KEY_ID
secretoptional
Source-bucket access key ID (S3 migration only)
format: 20-character uppercase alphanumeric beginning AKIA or ASIA
AWS IAM console > Users > your migration user > Security credentials > Create access key. The user needs s3:GetObject and s3:ListBucket on the source bucket for Sippy or Super Slurper to read misses.
AWS_SECRET_ACCESS_KEY
secretoptional
Source-bucket secret access key (S3 migration only)
format: 40-character base64-ish string, shown once
Returned alongside AWS_ACCESS_KEY_ID in the AWS IAM console at Users > Security credentials > Create access key.

Permissions to grant

Account Settings:ReadAPI token permission group (Account)

Lets `wrangler whoami` and /user/tokens/verify resolve and confirm the account ID before anything is provisioned.

โ†ณ Optional if CLOUDFLARE_ACCOUNT_ID is always supplied explicitly, but without it wrangler cannot list accounts and fails in non-interactive mode.

Workers Scripts:EditAPI token permission group (Account)

Uploads the Worker bundle and its bindings (`wrangler deploy`, `wrangler versions upload`, `wrangler versions deploy`, `wrangler rollback`) and stores secrets via `wrangler secret put`.

โ†ณ Workers Scripts:Read is enough for a review-only or cost-modelling engagement that never deploys.

Workers R2 Storage:EditAPI token permission group (Account)

Creates buckets, sets lifecycle rules, and enables/inspects Sippy for the incremental object-storage migration.

โ†ณ Workers R2 Storage:Read covers auditing existing buckets; note this permission governs the bucket control plane, while data-plane reads/writes use the separate R2 S3 access key pair.

Workers KV Storage:EditAPI token permission group (Account)

Creates the CONFIG namespace and writes/reads keys for feature flags and routing tables.

โ†ณ Workers KV Storage:Read suffices once namespaces exist and the Worker only reads at runtime.

D1:EditAPI token permission group (Account)

Creates the D1 database, applies migrations with `wrangler d1 migrations apply --remote`, and runs EXPLAIN QUERY PLAN / DDL through `wrangler d1 execute`.

โ†ณ D1:Read allows querying and plan inspection without schema changes; use it once the schema is frozen.

Queues:EditAPI token permission group (Account)

Creates the queue used for async fan-out, retries and dead-lettering (`wrangler queues create`).

โ†ณ Drop this permission entirely if step 2 of the procedure does not select Queues as a primitive.

Workers Tail:ReadAPI token permission group (Account)

Streams live logs during cutover with `wrangler tail --status=error` to catch 1101/1102 before shifting more traffic.

โ†ณ Omit for offline design work; it is only needed while observing a running deployment.

Zone:ReadAPI token permission group (Zone)

Resolves the zone by name to its Zone ID and reads the zone plan, which caps Cache Rules and WAF custom rule counts.

โ†ณ No narrower option; this is already read-only.

Workers Routes:EditAPI token permission group (Zone)

Attaches `app.example.com/*` to the Worker so the route in wrangler.jsonc actually binds on deploy.

โ†ณ Not needed if the Worker is invoked only on a workers.dev subdomain or via Service bindings.

Zone Settings:EditAPI token permission group (Zone)

Toggles the cache settings this skill PATCHes: tiered_cache_smart_topology_enable and cache_reserve.

โ†ณ Zone Settings:Read is enough for an audit that only reports current cache posture.

Zone WAF:EditAPI token permission group (Zone)

PUTs the entrypoint rulesets for the http_request_cache_settings, http_ratelimit and http_request_firewall_managed phases (Cache Rules, rate limiting, managed-ruleset Log-then-Block rollout).

โ†ณ Zone WAF:Read allows reviewing existing rulesets before you overwrite a phase entrypoint - do that first, because each PUT replaces the whole phase.

Cache Purge:PurgeAPI token permission group (Zone)

Purges by URL or prefix after a cache-key or TTL change so the new Cache Rule is what you are actually measuring.

โ†ณ There is no narrower purge scope; restrict blast radius by scoping the token to a single zone rather than All zones.

Step by step

  1. 1

    Enable the paid platform features the design depends on

    Cloudflare dashboard > Compute (Workers) > Plans > select Workers Paid, then Storage & Databases > R2 Object Storage > Purchase R2 / add payment method. Durable Objects, Queues, Cache Reserve and the 1000-subrequest limit are unavailable until Workers Paid is active, and `wrangler r2 bucket create` fails until R2 is subscribed.

    Open in the console / docs โ†—
  2. 2

    Record the account ID and the zone ID for every domain in scope

    Cloudflare dashboard > select the domain > Overview; in the right-hand API panel copy Zone ID and Account ID. Confirm the hostnames you plan to front are Proxied under DNS > Records - a grey-cloud record never reaches a Worker route, a Cache Rule or the WAF.

    Open in the console / docs โ†—
  3. 3

    Create one scoped API token with the exact permission set

    Go to https://dash.cloudflare.com/profile/api-tokens > Create Token > Custom token > Get started. Add Account permissions: Account Settings:Read, Workers Scripts:Edit, Workers R2 Storage:Edit, Workers KV Storage:Edit, D1:Edit, Queues:Edit, Workers Tail:Read. Add Zone permissions: Zone:Read, Workers Routes:Edit, Zone Settings:Edit, Zone WAF:Edit, Cache Purge:Purge. Under Account Resources pick the single account, under Zone Resources pick Specific zone and name the domain - not All zones. Optionally set Client IP Address Filtering and a TTL, then Continue to summary > Create Token and copy the value once.

    Open in the console / docs โ†—
  4. 4

    Export the token under both names wrangler and the curl snippets expect

    `export CLOUDFLARE_API_TOKEN='...'; export CF_API_TOKEN="$CLOUDFLARE_API_TOKEN"; export CLOUDFLARE_ACCOUNT_ID='...'; export ZONE_ID='...'`. wrangler reads CLOUDFLARE_API_TOKEN (and CLOUDFLARE_ACCOUNT_ID when the token sees more than one account); the Rulesets and cache-setting calls in this skill send Bearer $CF_API_TOKEN. Use `wrangler login` instead only for interactive local work.

    Open in the console / docs โ†—
  5. 5

    Install and pin the toolchain, then verify the account

    `npm i -D wrangler@4 && npx wrangler --version && npx wrangler whoami`. Check the returned account ID matches the intended account and that the permissions table lists the groups above. Set `compatibility_date` in wrangler.jsonc to a fixed recent date and never float it.

    Open in the console / docs โ†—
  6. 6

    Create the R2 S3-compatible access key pair (only if migrating object storage)

    Cloudflare dashboard > R2 Object Storage > API > Manage API tokens > Create API token, permission Object Read & Write, scoped to the destination bucket. Copy the Access Key ID and Secret Access Key into R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY - the account API token above cannot sign S3 requests, and `wrangler r2 bucket sippy enable` needs both this pair and the source-bucket pair.

    Open in the console / docs โ†—
  7. 7

    Grant the source bucket read access for Sippy or Super Slurper

    For S3: AWS IAM console > Policies > create a policy allowing s3:GetObject on arn:aws:s3:::legacy-assets/* and s3:ListBucket on arn:aws:s3:::legacy-assets, attach it to the migration user, then create an access key and export AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY. Super Slurper is the one-shot bulk copy for a read-only source; Sippy is the zero-downtime copy-on-miss path.

    Open in the console / docs โ†—
  8. 8

    Store runtime secrets with wrangler, never in vars or Terraform

    `npx wrangler secret put UPSTREAM_TOKEN` (repeat per secret) writes encrypted values bound to the Worker. Values placed in `vars` in wrangler.jsonc are plaintext in the dashboard and in your repo, and Terraform-managed Worker scripts leak secrets into state and fight `wrangler versions deploy`.

    Open in the console / docs โ†—
  9. 9

    Confirm the zone plan before promising rule counts

    Cloudflare dashboard > the domain > Overview shows the plan (Free / Pro / Business / Enterprise). The plan caps Cache Rules and WAF custom rules and gates Bot Management, Logpush and Tiered Cache topology; non-Enterprise rate-limiting rules must include cf.colo.id in `characteristics` or the PUT is rejected.

    Open in the console / docs โ†—
  10. 10

    Dry-run a deploy and a read-only API call before touching production

    `npx wrangler deploy --dry-run --outdir=dist` to check bundle size and startup cost, then GET the current cache-phase ruleset (see verify command and apiOps) so you know exactly what a PUT to the phase entrypoint would replace.

    Open in the console / docs โ†—

Check it worked

curl -sS -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" https://api.cloudflare.com/client/v4/user/tokens/verify

If you hit an error

In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work.

Cause: wrangler found no OAuth session from `wrangler login` and no token in the environment (a token exported only as CF_API_TOKEN does not count).

Fix: Export CLOUDFLARE_API_TOKEN with the scoped token, or run `npx wrangler login` for interactive use. Export CF_API_TOKEN as well so the curl snippets in this skill authenticate too.

More than one account available but unable to select one in non-interactive mode.

Cause: The token can see multiple accounts and wrangler cannot prompt in CI.

Fix: Set CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc) to the 32-hex account ID printed by `wrangler whoami`.

Authentication error [code: 10000]

Cause: The token is missing the permission group for the resource, is scoped to the wrong account or zone, has expired, or is blocked by its IP filter. Cloudflare returns the same generic code for insufficient scope as for a bad token.

Fix: Re-open the token at https://dash.cloudflare.com/profile/api-tokens, add the missing group (for example Zone WAF:Edit for ruleset PUTs, D1:Edit for migrations), confirm Zone Resources includes the zone, then re-verify with /user/tokens/verify.

Could not route to /client/v4/zones/.../rulesets/phases/http_request_cache_settings/entrypoint, perhaps your object identifier is invalid? [code: 7003]

Cause: ZONE_ID is empty or wrong, so the path contains a blank or malformed identifier - usually the shell variable was never exported into the subshell running the curl.

Fix: echo "$ZONE_ID" to confirm 32 hex characters, re-copy it from the domain Overview page, and re-run the request.

cf-cache-status: DYNAMIC

Cause: No Cache Rule matched the request, so Cloudflare treated the response as uncacheable by default.

Fix: Check the rule expression really matches the path, confirm the hostname is Proxied, and re-issue the PUT to the http_request_cache_settings phase entrypoint. If the status is BYPASS instead, the origin is sending Set-Cookie or Cache-Control: private/no-store - override edge TTL and normalize the cache key.

error code: 1101 (Worker threw exception)

Cause: An uncaught exception inside the Worker, most often an unhandled rejection or a null binding because the binding name in wrangler.jsonc does not match the code.

Fix: Run `npx wrangler tail --format=pretty --status=error` to see the stack, wrap the handler in try/catch, and confirm every binding name (ASSETS, DB, CONFIG, ROOM) exists in the deployed version.

error code: 1102 (Worker exceeded resource limits)

Cause: CPU time exceeded - commonly buffering a large R2 object with arrayBuffer(), or heavy synchronous work per request.

Fix: Stream R2 bodies with `new Response(obj.body)`, move non-blocking work into ctx.waitUntil(), and raise `limits.cpu_ms` only after profiling.

Script startup exceeded CPU time limit. [code: 10021]

Cause: Module-scope initialization (large parsing, client construction, WASM instantiation) runs during the deploy-time startup check.

Fix: Initialize lazily inside the fetch handler behind a cached promise instead of at module scope, then redeploy.

D1_ERROR: no such table: orders: SQLITE_ERROR

Cause: Migrations were applied with --local only, or to a different database than the one bound by database_id in wrangler.jsonc.

Fix: Run `npx wrangler d1 migrations apply <db> --remote` and confirm the database_id in wrangler.jsonc matches the ID printed by `wrangler d1 create`.

Error 1015: You are being rate limited

Cause: A rate-limiting rule you deployed is matching real traffic - frequently because the characteristics are too coarse (ip.src alone behind a corporate NAT or a mobile carrier).

Fix: Widen requests_per_period, add a narrower expression, or add characteristics such as a session identifier. Remember non-Enterprise rules must still include cf.colo.id, which makes the effective limit per data centre.

Official documentation: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ โ†—

API operations this skill uses (20)

Every call the skill makes, linked to its official reference page.

OperationCallPermissionRef
Verify API token
Proves the token is active before provisioning; the 200/`"status":"active"` response is the skill's verification gate.
GET /client/v4/user/tokens/verifyAny valid token (no permission group required)docs โ†—
Identify the account (wrangler whoami)
Step 1 of the procedure - confirm the account ID and the token's permission list before touching anything.
CLI npx wrangler whoamiAccount Settings:Readdocs โ†—
Create R2 bucket
Creates the egress-free object store that replaces S3/CloudFront in the cost model, with a location hint near the write path.
POST /client/v4/accounts/{account_id}/r2/buckets (CLI: npx wrangler r2 bucket create acme-assets-prod --location wnam)Workers R2 Storage:Editdocs โ†—
Add R2 object lifecycle rule
Transitions cold prefixes to Infrequent Access so stored-byte cost drops from ~$0.015 to ~$0.01 per GB-month.
PUT /client/v4/accounts/{account_id}/r2/buckets/{bucket_name}/lifecycle (CLI: npx wrangler r2 bucket lifecycle add ... --storage-class InfrequentAccess --transition-days 30)Workers R2 Storage:Editdocs โ†—
Enable Sippy incremental migration
Zero-downtime cutover - R2 serves what it holds and copies misses from S3 on first read until source GET volume flatlines.
PUT /client/v4/accounts/{account_id}/r2/buckets/{bucket_name}/sippy (CLI: npx wrangler r2 bucket sippy enable)Workers R2 Storage:Edit plus source-bucket read credentialsdocs โ†—
Create D1 database
Provisions the SQL primitive for joins and per-tenant tables under ~10 GB; the returned database_id goes into wrangler.jsonc.
POST /client/v4/accounts/{account_id}/d1/database (CLI: npx wrangler d1 create acme-app-prod)D1:Editdocs โ†—
Apply D1 migrations remotely
Versions the schema; running with --local only is the cause of the D1_ERROR: no such table failure at runtime.
CLI npx wrangler d1 migrations apply acme-app-prod --remoteD1:Editdocs โ†—
Run query / EXPLAIN QUERY PLAN against D1
D1 bills rows read, so the skill asserts every hot-path query shows SEARCH ... USING INDEX and never SCAN.
POST /client/v4/accounts/{account_id}/d1/database/{database_id}/query (CLI: npx wrangler d1 execute acme-app-prod --remote --command "EXPLAIN QUERY PLAN ...")D1:Edit (D1:Read for read-only statements)docs โ†—
Create Workers KV namespace
Holds feature flags and routing tables that tolerate ~60 s global propagation; the returned id is pasted into kv_namespaces.
POST /client/v4/accounts/{account_id}/storage/kv/namespaces (CLI: npx wrangler kv namespace create CONFIG)Workers KV Storage:Editdocs โ†—
Create Queue
Provides async fan-out, retries and dead-lettering for work moved off the request path.
POST /client/v4/accounts/{account_id}/queues (CLI: npx wrangler queues create acme-jobs)Queues:Editdocs โ†—
Upload / deploy the Worker script with bindings
Ships src/index.ts together with the R2, D1, KV and Durable Object bindings and the pinned compatibility_date.
PUT /client/v4/accounts/{account_id}/workers/scripts/{script_name} (CLI: npx wrangler deploy / npx wrangler versions upload)Workers Scripts:Editdocs โ†—
Gradual deployment / rollback of a Worker version
Splits traffic (for example 10% new / 90% current) during cutover and gives the one-command revert the runbook requires.
CLI npx wrangler versions deploy | npx wrangler rollbackWorkers Scripts:Editdocs โ†—
Set a Worker secret
Keeps origin credentials out of `vars` and out of Terraform state, as the quality checklist demands.
CLI npx wrangler secret put UPSTREAM_TOKENWorkers Scripts:Editdocs โ†—
Stream live Worker logs
Watches for 1101/1102/1015 during the traffic ramp before increasing the new version's share.
CLI npx wrangler tail --format=pretty --status=errorWorkers Tail:Readdocs โ†—
Enable Smart Tiered Cache topology
Adds an upper-tier data centre so misses collapse toward one origin fetch instead of one per edge colo.
PATCH /client/v4/zones/{zone_id}/cache/tiered_cache_smart_topology_enableZone Settings:Editdocs โ†—
Enable Cache Reserve
Persists cacheable objects in R2-backed storage so origin egress keeps dropping after edge TTLs expire.
PATCH /client/v4/zones/{zone_id}/cache/cache_reserveZone Settings:Editdocs โ†—
Replace the Cache Rules phase entrypoint ruleset
Installs set_cache_settings rules (edge/browser TTL override, query-string-order-insensitive cache key) for content-hashed build output. The PUT replaces the entire phase, so GET it first.
PUT /client/v4/zones/{zone_id}/rulesets/phases/http_request_cache_settings/entrypointZone WAF:Editdocs โ†—
Replace the rate-limiting phase entrypoint ruleset
Deploys the login guard; on non-Enterprise plans the characteristics array must include cf.colo.id or the ruleset fails validation.
PUT /client/v4/zones/{zone_id}/rulesets/phases/http_ratelimit/entrypointZone WAF:Editdocs โ†—
Configure managed WAF rulesets (Log then Block)
Deploys the Cloudflare Managed Ruleset and OWASP Core Ruleset in Log mode, then flips to Block after 48 h of false-positive review.
PUT /client/v4/zones/{zone_id}/rulesets/phases/http_request_firewall_managed/entrypointZone WAF:Editdocs โ†—
Purge cache after a rule or TTL change
Clears stale entries so the cf-cache-status: HIT verification measures the new Cache Rule rather than the old cache key.
POST /client/v4/zones/{zone_id}/purge_cacheCache Purge:Purgedocs โ†—