skills/cloudflare-setup/SKILL.md
One-time Cloudflare API token setup so other skills (domain, cloudflare-dns, cf-ship) can create zones, edit DNS, manage Workers, and write redirect rules. Use when a Cloudflare skill fails with "Authentication error" or when onboarding a new machine. Walks the user through the account-owned token + user token model that Cloudflare requires.
npx skillsauth add RonanCodes/ronan-skills cloudflare-setupInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
One-time setup. Mints the two API tokens the automation skills need and writes them to ~/.claude/.env. Run when:
Authentication error (code 10000) or request is not authorizedCLOUDFLARE_API_TOKEN_* set/ro:cloudflare-setup # full interactive setup
/ro:cloudflare-setup --verify # test both tokens, report what works
/ro:cloudflare-setup --reset # clear existing values and start fresh
Cloudflare has two kinds of API token and they expose different permissions. This is the single biggest source of confusion when automating Cloudflare — missing it costs 30+ minutes of dashboard clicking.
| Token type | Created at | Can do |
|------------|-----------|--------|
| User token | /profile/api-tokens | Day-to-day zone config: DNS edits, rulesets, worker deploys, worker custom domains |
| Account-owned token | Account → Manage Account → API Tokens | Zone creation (com.cloudflare.api.account.zone.create), account-level scopes that aren't available via user tokens |
You need both because:
The Global API Key is a third option that can do everything, but it's also all-access (billing, account delete, zero-scope). Avoid unless you have a very specific reason.
Writes to ~/.claude/.env:
# Account-owned — for onboarding new domains (zone creation, account-level ops)
CLOUDFLARE_API_TOKEN_ZONE_CREATE=cfat_... # prefix cfat_ = account-owned
CLOUDFLARE_ACCOUNT_ID=...
# User token — for day-to-day DNS, Worker, redirect rule management
CLOUDFLARE_API_TOKEN_ADMIN=cfut_... # prefix cfut_ = user token
CLOUDFLARE_ZONE_ID=... # optional: primary zone you deploy to
# Legacy — kept for backwards-compat with older skills
CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN_ADMIN
The cfat_ vs cfut_ prefix is the fastest way to tell them apart when reading .env. cfat_ = account (admin), cfut_ = user.
source "$(ro context env)" 2>/dev/null
echo "ZONE_CREATE: ${CLOUDFLARE_API_TOKEN_ZONE_CREATE:+set}${CLOUDFLARE_API_TOKEN_ZONE_CREATE:-missing}"
echo "ADMIN: ${CLOUDFLARE_API_TOKEN_ADMIN:+set}${CLOUDFLARE_API_TOKEN_ADMIN:-missing}"
echo "ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID:+set}${CLOUDFLARE_ACCOUNT_ID:-missing}"
If all three are set, offer --verify as the likely next step. If any missing, proceed to mint them.
Easiest path — the user can read it off the dashboard: https://dash.cloudflare.com → any zone → right sidebar "API" section → Account ID. Paste to you.
Alternatively, if they already have any valid API token, fetch it:
curl -s "https://api.cloudflare.com/client/v4/accounts" \
-H "Authorization: Bearer $EXISTING_TOKEN" \
| jq -r '.result[] | "\(.id) \(.name)"'
If there's only one account, use it. Otherwise ask the user which.
This lives in a different place than the user-profile tokens. UI path:
zone-bootstrap (or any descriptive name)Save:
echo 'CLOUDFLARE_API_TOKEN_ZONE_CREATE=<paste-cfat-token>' >> ~/.claude/.env
echo 'CLOUDFLARE_ACCOUNT_ID=<account-id>' >> ~/.claude/.env
Verify it works with an authenticated call (account-owned tokens do not work against /user/tokens/verify, so test with a real call):
source "$(ro context env)"
curl -s "https://api.cloudflare.com/client/v4/zones?per_page=1" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ZONE_CREATE" \
| jq '{success, errors}'
success: true means the token is valid. Auth errors mean the token wasn't saved correctly or the scope is wrong.
UI path:
claude-skills (or similar)Save:
echo 'CLOUDFLARE_API_TOKEN_ADMIN=<paste-cfut-token>' >> ~/.claude/.env
# Maintain backwards-compat for skills that read the legacy name:
echo 'CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN_ADMIN' >> ~/.claude/.env
Verify:
source "$(ro context env)"
curl -s "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" \
| jq '{success, status: .result.status}'
Expect success: true, status: "active".
If there's a "main" zone you deploy to repeatedly, cache its ID to skip lookups:
PRIMARY_ZONE=<your-main-domain.com>
ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=$PRIMARY_ZONE" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" | jq -r '.result[0].id')
echo "CLOUDFLARE_ZONE_ID=$ZONE_ID" >> ~/.claude/.env
--verify modeRun both tokens through a minimal set of calls to prove each scope:
source "$(ro context env)"
echo "--- account-owned token (zone create) ---"
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ZONE_CREATE" \
| jq '{success, name: .result.name}'
echo "--- user token: list zones ---"
curl -s "https://api.cloudflare.com/client/v4/zones?per_page=5" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" \
| jq '{success, count: (.result|length)}'
# Pick any zone id you own, then:
echo "--- user token: read rulesets (Config Rules) ---"
curl -s "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/rulesets" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" \
| jq '{success}'
echo "--- user token: read dynamic redirect phase ---"
curl -s "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN_ADMIN" \
| jq '{success, errors}'
Expected:
success: true → fully set up.success: true but phase entrypoint 10003 "could not find entrypoint" → authorized but no rule exists yet (this is fine, not an error).10000 (Authentication error) → token is missing that scope. Re-check UI.--reset modeBack up the current .env, then remove the Cloudflare block:
cp ~/.claude/.env ~/.claude/.env.bak.$(date +%Y%m%d-%H%M%S)
# Strip any line starting with CLOUDFLARE_
sed -i.tmp '/^CLOUDFLARE_/d' ~/.claude/.env && rm ~/.claude/.env.tmp
Then restart at step 2.
| Symptom | Cause | Fix |
|---------|-------|-----|
| Authentication error (10000) on zone create | Using a user token for POST /zones | Switch to the account-owned (cfat_) token |
| Authentication error on redirect rule PUT | User token missing Dynamic URL Redirect scope | Edit the user token, add Dynamic URL Redirect > Edit |
| request is not authorized on specific phase | Token has Config Rules but not Dynamic URL Redirect | Same — they are separate permissions despite being adjacent features |
| could not find entrypoint ruleset (10003) | No error — just means no rule has been created yet | Continue; first PUT creates it |
| Invalid API Token on any call | Wrong token pasted, or trailing whitespace | Re-check the saved value with grep ^CLOUDFLARE ~/.claude/.env |
After this skill runs, ~/.claude/.env is the source of truth for every Cloudflare token. Other skills (/ro:cf-ship, /ro:cloudflare-dns, /ro:migrate-to-tanstack, /ro:new-tanstack-app, stack-audit) must source from there before asking the user for a token. Pattern:
set -a && source "$(ro context env)" && set +a
unset GH_TOKEN GITHUB_TOKEN # ~/.claude/.env's GITHUB_TOKEN shadows gh CLI keychain — must unset
Then for the legacy CLOUDFLARE_API_TOKEN name (what wrangler reads):
export CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN_ADMIN
export CLOUDFLARE_ACCOUNT_ID
pnpm exec wrangler deploy
For mirroring tokens into a GitHub repo's production environment so deploy.yml can use them:
gh api -X PUT repos/$OWNER/$REPO/environments/production # idempotent
printf '%s' "$CLOUDFLARE_API_TOKEN_ADMIN" | gh secret set CLOUDFLARE_API_TOKEN --env production -R $OWNER/$REPO
printf '%s' "$CLOUDFLARE_ACCOUNT_ID" | gh secret set CLOUDFLARE_ACCOUNT_ID --env production -R $OWNER/$REPO
~/.claude/.env manually.dev.vars (wrangler secrets) — different concept; see /ro:cf-shipThis skill mints API tokens; it does not set up app auth. But the canonical way to protect a single-user / internal / personal app on Workers is Cloudflare Access (edge gate, so the app is never publicly reachable) + WARP device posture (only enrolled devices pass) + a phishing-resistant passkey at the IdP. This is the Tailscale-equivalent on Workers (Tailscale gates self-hosted boxes, not Workers). Full standard: the authentication-hardening playbook (llm-wiki-security/wiki/playbooks/authentication-hardening.md); /ro:new-tanstack-app offers this as the recommended auth path for personal apps.
/ro:domain — register domains on Porkbun, ties into this skill for the NS handoff to Cloudflare/ro:cloudflare-dns — day-to-day DNS edits (uses the user token from step 4)/ro:cf-ship — deploy a Worker (uses the user token)development
--- name: worktree description: Coordinate multiple agents on one repo via a worktree-lock pool, so two agents never clobber each other's working tree. Acquire the first free slot (main, then beta/gamma… worktrees, created on demand), work there on your own branch, release when you've pushed. Use before modifying any repo that might be in use by another agent (factory, dataforce, etc.), or whenever you're told a repo is being worked on. Backed by `ro worktree`. category: development argument-hin
testing
--- name: ship description: Ship a feature branch the local-CI-first way — run the full local gate, push, open a PR, squash-merge, then deploy, without waiting on GitHub Actions. Use when a branch is ready for main and you want it merged and deployed now. Reads CI policy from `ro ci` (default skips remote CI because GitHub Actions billing keeps hitting limits). Sibling to /ro:gh-ship (waits on GitHub checks) and /ro:cf-ship (the deploy half). Triggers on "ship it", "ship this", "merge and deploy
testing
--- name: setup-logging description: Set up (or audit) the observability stack in a TanStack Start + Cloudflare Workers app so it is "diagnosable by default" — structured logging (logtape) with a request context carrying trace_id + userId + tenant/orgId, a trace_id propagated FE→BE→logs→Sentry→PostHog, Cloudflare Workers observability enabled, and Sentry + PostHog wired. Two modes: `setup` (wire it into an app) and `audit` (check an existing app + report gaps). Use when scaffolding a new app, wh
development
Manage credentials INSIDE the active ~/.claude/.env file — read which token/account to use for a given app (Simplicity vs Dataforce vs Ronan-personal), add or update a secret WITHOUT it passing through the chat (an interactive Terminal window prompts for it), and track secrets that were exposed in a transcript so they get rotated. Sibling to /ro:context (which switches WHICH env file is active). Use when the user wants to add an API key/token/secret, asks "which credential do I use for X", needs the env organized/labelled, or a secret was pasted into the chat and should be rotated.