Cloudflare account, the Gateway and its owner
Step-by-step guide: the Cloudflare account, Workers Paid, a domain, everyapp setup, the owner account, the open apps, and the check every other setup guide relies on.
Everything else in these guides needs this first: a Cloudflare account on
Workers Paid with a domain, the Gateway Worker installed at <gateway host>
(apps.<your domain> unless the person chooses another name), the cf CLI
logged in, and an owner account. Every app uses it: OpenSEO, Open CRM,
openmail, OpenAnalytics, and anything everyapp create makes. At
the end the person has https://<gateway host> to sign in to,
https://<gateway host>/admin to invite people, and apps at
<app>.<app base domain>.
Settings
The first four rows setup writes itself and secret set refuses
(SECRET_NOT_SETTABLE; it also refuses APP_BASE_DOMAIN and
THIRD_PARTY_DEPLOYS, which setup doesn't write). The last two are optional
and set with secret set (Step 9).
| NAME | required or optional | what it holds |
|---|---|---|
GATEWAY_URL |
set by setup | https://<gateway host> |
CLOUDFLARE_ACCOUNT_ID |
set by setup | the account's id (32 hex characters); Workers AI and email reuse it |
GATEWAY_DEPLOYMENT_MODE |
set by setup | self_hosted or hosted; set at the first upload, never changes |
BETTER_AUTH_SECRET, JWT_PRIVATE_KEY, JWT_PUBLIC_KEY |
set by setup | the Gateway's session and identity keys (rotation: docs/runbooks/key-rotation.md) |
CF_DEPLOY_TOKEN |
optional | the Gateway's own Cloudflare API token, for Gateway app installs (everyapp deploy-token add) |
BROWSER_APP_INSTALLS |
optional | exactly on turns Gateway app installs on (everyapp deploy-token add writes it with the token); unset, off or anything else keeps them off. Owners and admins of the install organization, from a session under 10 minutes old |
Costs and plans
As of October 2026. A Cloudflare account is free. Workers Paid is a $5 a
month minimum per account, then usage; it includes 10 million requests, 30
million CPU ms a month and D1's 25 billion rows read and 50 million written
a month plus 5 GB of storage, far more than a small team's Gateway uses.
The Free plan is refused: D1 Free stops answering at 5 million rows read or
100,000 written a day (reset 00:00 UTC), and the Gateway reads D1 on every
request. A domain: Cloudflare Registrar sells at cost, with no promotions
of its own; price trackers list a .dev or .app at about $8 the first
year (a registry discount), then $12.20 or $14.20 a year, and a .com at
about $10.46 (setup's own message says about $10); the dashboard shows the
exact price before Complete purchase. Moving a domain you own is free,
on the Free zone plan. Node, pnpm, git and cf are free. No rate limits
matter; the device login gives 5 minutes (cf's own limit), the owner link
lasts 24 hours.
Only a person can
- Sign up for Cloudflare and verify the email.
- Add a card and subscribe to Workers Paid.
- Buy a domain, or change nameservers at the registrar they bought it from.
- Approve the device login in a browser, and pick a workers.dev subdomain when Cloudflare asks for one.
- Open the owner link and create the account (name, email, password).
- Choose the Cloudflare account when the login sees several (you show the choices).
- Enable R2 in the dashboard once, before OpenSEO, OpenCRM or OpenMail installs on an account that never had it (Step 11).
Before you start
- Tools:
node --versionprints 22.18.0 or newer (22 and 24 are LTS; 26, the Current release, works too),pnpm --versionandgit --versionanswer, andeveryapp --versionprints 0.3.0 or newer (npm install -g everyapp@latestinstalls or updates it; it brings its own pinnedcf). No clone is needed: setup downloads the Gateway release for this everyapp version. (In a checkout of the EveryApp repository,everyappbuilds the Gateway from the tree instead.) everyapp status --json.donewithdata.installed: trueanddata.claimed: true: the Gateway exists and has an owner, go to Verify (aGATEWAY_OUTDATEDfix ofseverity: "info"is not a reason to stop; one ofseverity: "error"means runeveryapp updatebefore anything else).action_required install_gateway(exit 2;data.reruniseveryapp setup): no Gateway in any account this login sees, do the Steps.action_required cloudflare_loginwithwaiting: true: hand overdata.urlanddata.code(Step 6) and keep reading.donewithdata.installed: falseand aGATEWAY_NOT_INSTALLEDfix, or fixesGATEWAY_NOT_UPLOADEDorNO_OWNER: an earlier setup stopped, run Step 7 again. ErrorGATEWAY_NOT_FOUND: the account named by--account-idorCLOUDFLARE_ACCOUNT_IDhas no Gateway; its message says where one is.- Once
statussaysdata.installed: true:everyapp secret list --json;data.gateway.nameshasGATEWAY_URL, andCF_DEPLOY_TOKENin it means Gateway app installs are configured already. On a fresh account skip it:secret list,email showandstatusall need the Cloudflare login (under--jsoneach starts the samecloudflare_loginitself) and an installed Gateway; with none,statusstops oninstall_gatewaywhile the other two endGATEWAY_NOT_FOUND(exit 1), and an install that stopped before its first upload ends themGATEWAY_NOT_INSTALLED(exit 1,data.rerunis setup). - Which account. Setup checks the rest itself: it logs in (
setup.auth;statusstarts the samecloudflare_login), reads the plan (setup.plan) and the domain (setup.domain) and stops oncloudflare_login,choose_account,upgrade_workers_paid,add_domainorZONE_NOT_FOUND(Step 7), so on a first install nothing here gates it.cf auth whoami(read-only) prints the login's email and its accounts: several, ask the person which and carry--account-id <account id>on every command. When setup stops:cf zones listshows the domains and whether each isactive;cf accounts subscriptions getis what setup reads for the plan.
Steps
Person: Create the Cloudflare account, or use the one they have. Open https://dash.cloudflare.com/sign-up, enter Email and Password, select Create Account. Open Cloudflare's verification email (check Spam) and follow its link, then log in. Check: My Profile → Email Address shows "(verified)"; if Send verification email is offered there, it isn't yet. A verified email is required to buy a domain, and Cloudflare refuses to create anything (API code 10034) until it is: setup would end
CF_EMAIL_UNVERIFIED(Step 7). Wait for "verified".Person: Subscribe to Workers Paid. Open https://dash.cloudflare.com/?to=/:account/workers/plans (or
https://dash.cloudflare.com/<account id>/workers/plans), choose the Paid plan ($5 a month), enter a card (Visa, Mastercard, American Express, Discover, UnionPay, PayPal or a wallet) and complete the purchase. A card can also be added first under Billing → Subscriptions → Payment methods → Add Payment Method → Confirm. Done when Billing → Subscriptions lists Workers Paid. You can check:cf accounts subscriptions getnames a Workers plan containing "paid" or "standard".Person: Get a domain on Cloudflare, in the same account. Buy: open https://dash.cloudflare.com/?to=/:account/registrar/register, search a name, select Purchase, pick the term under Payment option, enter contact details (ASCII only) and payment, select Complete purchase (up to 30 seconds; a confirmation email follows). Registrar charges only the primary payment method (card, PayPal or Apple Pay): with several on the account, make the right one primary first under Billing → Payment methods (Make primary payment method). Move one they own: open https://dash.cloudflare.com/?to=/:account/domains/overview, select Onboard a domain, enter the apex without
www, Continue, choose the Free plan, check the copied DNS records (mail records above all) and Continue, then replace the nameservers at the current registrar with the two Cloudflare shows (turn DNSSEC off there first). Done when Cloudflare's email says the domain is active (usually under an hour, up to a day). Wait forcf zones listto show itactive.You: Install the tools if Before you start found them missing (Node 22.18.0 or newer from https://nodejs.org, 22 or 24 LTS or 26;
npm install -g pnpm; git from https://git-scm.com on Windows;npm install -g everyapp@latest).You:
everyapp login --json, when nothing has logged in yet (statusand setup start the same device login themselves; on its own this is for seeing the accounts withcf auth whoamibefore choosing--account-id, or aftercf auth logout). Already logged in: astep cloudflare.login skipwithdata.email, thendone. Otherwise anaction_required cloudflare_loginwithdata.url,data.code,data.expiresAtandwaiting: true: give the person the link and the code at once (Step 6; cf gives up after 5 minutes). The run ends withstep cloudflare.login ok("Logged in to Cloudflare.",data.email) anddone, exit 0. Underneath it iscf auth login --no-browser. Unattended runs (CI) exportCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDinstead (the token takes priority over any stored login): a custom token made at https://dash.cloudflare.com/profile/api-tokens with Account › Workers Scripts › Edit, Account › D1 › Edit, Account › Workers KV Storage › Edit, Zone › Zone › Read (listing the domains needs it) and Zone › DNS › Read, limited to this account and the domain's zone. A token that lists no accounts needs--account-id(ACCOUNT_REQUIRED). The owner link is withheld only when theCIvariable is set, or output is piped without--json(thenOWNER_NOT_CLAIMED); a token login with--jsonon the person's machine still getsclaim_owner.Person: Approve the login, within 5 minutes. Open
data.url(Cloudflare's device page; the code is not in the link), sign in to Cloudflare if asked, typedata.codewhere the page asks for it, and approve the request (Cloudflare's docs: "Approve the request to give cf access to your Cloudflare account"). The terminal moves on: wait for thestep … ok"Logged in to Cloudflare" event. Over SSH or in a container the same link works from any browser. Datacenter IPs get a 403 managed challenge from the OAuth endpoints (cloudflare/cf issue 23, open): use the API token instead.You:
everyapp setup --domain apps.<your domain> --json(add--account-id <account id>when the login sees several accounts). Its events, in order:event what it carries setup.toolchaindata.node,data.cfsetup.authdata.email; before it,cloudflare_loginwhen nothing is logged insetup.accountdata.id,data.name;data.gatewaytrue when one existssetup.plannew install only: okon Paid;warning WORKERS_PAID_UNVERIFIEDwhen it can't tell (the run goes on);skipwhen cf couldn't read itsetup.domaindata.gatewayHost,data.appBaseDomain,data.zonesetup.gatewaystart(data.change:install,resumeafter an earlier stop,upgrade,repairorredeploy; on a fresh installdata.expectedMinutes{ min: 5, max: 10 }, the human line adding "usually 5–10 minutes, then the owner link": a whole first run takes 5 to 20 minutes, most of it waiting at that link, so run setup with a long timeout or in the background and keep reading its events); a minute or two of silence while the checkout builds (nothing is printed under--json: don't kill the run), thenstep gateway.release ok("Built the Gateway … from this checkout." or "Using the Gateway … built from this checkout before"),gateway.resources(the D1 database and KV namespace, created or found), DNS, the Worker, its certificate,oksetup.ownerskip"Owner account is <email>" when one exists; elseclaim_owner(Step 8), thenoksetup.accessCloudflare Access, on by default once the owner exists (self-hosted, not when it is on already). Under --jsonit goes on without a question:access on's steps, thendone.data.accessOn: true. Stops (exit 1) onACCESS_TEAM_REQUIREDwhen the account has no Zero Trust team: ask the person for the name (access.md step 1: permanent;data.suggestedis a suggestion), then run this command again with--team <name>.ACCESS_SETUP_FAILED: show its fix, then run setup again (it picks up at Access). Only--no-access(never added unless the person says so), a No at a terminal, or an earlieraccess offleaves it off, remembered for later runs:warning ACCESS_SETUP_SKIPPED(data.reason) says it isn't recommended; relay itdonedata.gatewayUrl,data.adminUrl,data.owner,data.gateway(installed,upgraded,repairedorup-to-date),data.next:[{ command, note }]. On a fresh self-hosted install, or when the owner was just created, these lines in this order:everyapp create ../my-app,everyapp access on(recommended; left out whendata.accessOnis true, with the factAccess on),everyapp secret set OPENAI_API_KEY(orOPENROUTER_API_KEY,WORKERS_AI_API_TOKEN),everyapp secret set EMAIL_FROM EMAIL_FROM_NAME. Show them from the event; whenaccess onis among them and the person didn't say no, thesetup.accessrow applies; hosted: no Access line; up to date: onlystatusWhat it can stop on (exit 2, rerun
data.rerunonce done):cloudflare_login(waiting: true): Step 6.choose_account: several accounts and no Gateway, or Gateways in several accounts.data.choices[]hasaccountId,accountName,labeland a readyreruneach (--account-id <account id>, or--domain <gateway host>when the host picks it);gatewayUrlonly when the choice is between existing Gateways. Ask the person which, run thatrerun.upgrade_workers_paid("Subscribe this account to Workers Paid";data.urlthe plans page,data.plan): the account is on Free, nothing was created. Step 2, then rerun. Cloudflare refusing the upload on the Free plan ends the same way.add_domain(data.url): "Finish activating": a domain whose nameservers don't point at Cloudflare yet (url: that domain's page,data.zone,data.status); "Add a domain": only when setup ran without--domainand the account has none (url: the domains overview). With--domain, no domain is the errorZONE_NOT_FOUNDbelow. Step 3, then rerun.register_workers_subdomain(data.url,…/workers/onboarding): Cloudflare wants a workers.dev subdomain before the account's first Worker. The person opens the link and picks any name; apps never use it. If that page shows no name field, open Workers & Pages and select Change next to Your subdomain (or https://dash.cloudflare.com/?to=/:account/workers/subdomain). At a terminal cf offers to register one itself. It stops the Worker upload, after the D1 database, the KV namespace and the install row exist: a rerun (--domainmay be left off) resumes (setup.gateway startwithdata.change: "resume") and creates nothing twice.rerun_interactive(data.needsTerminal: true): the Gateway Worker serves a version everyapp didn't deploy (the dashboard, the API, an interrupted deploy), and nothing replaces it unattended.data.rerunis this command without--json,data.rerunNote"in a terminal" (or "…and confirm cf's prompt"): hand it to the person for their own terminal; never run it yourself, with or without--json.claim_owner(waiting: true): Step 8. Errors (exit 1):DOMAIN_REQUIRED(unattended without--domain;data.zoneslists the active domains. One zone:data.rerunis this command with--domain apps.<zone>,data.rerunNote"or another name under <zone>". Several:data.choices[],{ label, rerun }per zone and nodata.rerun: ask the person which, run itsrerun),CF_EMAIL_UNVERIFIED("Verify your Cloudflare account's email address first": Cloudflare API code 10034;data.urlhttps://dash.cloudflare.com/profile,data.rerunthis command: Step 1, then rerun),ZONE_NOT_FOUND(no domain in this account, or--domainisn't under one;data.urlthe domains overview,data.zonesthe active domains, empty when there are none: Step 3, then rerun),DNS_RECORD_CONFLICT(something already answers the name;data.fix: delete the record under the domain's DNS page, or another name; itsdata.rerunadds--force-dnswithdata.authorize: true: ask first, never add the flag on your own),DNS_UNREADABLE(an API token without Zone › DNS › Read can't tell whether a record is in the way;data.fixsays to give it that; the same--force-dnsrule),DOMAIN_CHANGE_UNSUPPORTED(installed elsewhere; rerun without--domain),ACCOUNT_NOT_ACCESSIBLE(lists the accounts the login can use),ACCOUNT_REQUIRED(an API token that lists no accounts: pass--account-id),UNSUPPORTED_RUNTIME(Node too old, or Bun).
Person: Create the owner account.
action_required claim_ownercarriesdata.url(https://<gateway host>/setup#token=est_…),data.expiresAt(24 hours),data.replacedandwaiting: true(data.waitingFor: "the owner account (up to 15 minutes)"); setup polls for 15 minutes. Show the link to the person and to nobody else: whoever opens it first owns the Gateway, so it never goes into a log, an issue, a commit or a file. At a terminal setup opens it itself. The page is titled Set up your gateway ("Create the owner account for this gateway."): Name, Email, Password (at least 8 characters), Confirm password, then Create owner account; the button reads "Setting up..." and they land in the Gateway, signed in. Before it, awarning GATEWAY_UNREACHABLEmeans the certificate is still on its way (the link works as soon as the host answers);ACCESS_IN_FRONTmeans they sign in to Cloudflare Access first. Wait forstep setup.owner ok("Owner account created for <email>") anddone. Nobody opened it in 15 minutes: exit 2claim_ownerwithdata.setupUrl; the same link still works untildata.expiresAt, and a rerun before anyone opens it mints a new one and kills it (data.replaced: true). Once they say the account exists after that exit 2: rundata.rerunas given (under--jsonit is exactlyeveryapp setup --json; every rerun keeps the flags the run had, except aneedsTerminal: trueone, which the person runs); it finds the owner (step setup.owner skip, "Owner account is <email>"; no new link once there is one) and endsdonewithdata.gateway: "up-to-date"anddata.adminUrl. Oreveryapp status --json:data.claimed: trueanddata.owner.emailsay the same. "Use the newer setup link": setup ran again elsewhere, or it expired; rerun for a new link.Gateway app installs are not part of setup: off by default, never asked. Mention them only when the person asks to install apps from the browser; then they run
everyapp deploy-token addat a terminal themselves (under--jsonit stopsrerun_interactivewithneedsTerminal; reference: https://everyapp.com/docs/agent/deploy#gateway-app-installs).You:
everyapp status --json, read as in Verify.You, then the person: The four EveryApp apps, offered once, as the setup prompt does (landing-page/content/docs/setup-prompt.txt, step 4): "I'll install OpenSEO, OpenCRM, OpenAnalytics and OpenMail. Any you don't want?" For each one they keep, from the
every-appfolder:everyapp install https://github.com/every-app/<repo> --jsonwithopen-seo,open-crm,open-analyticsoropen-mailas<repo>. It downloads the repository's latest release (everyapp-release.tar.gz, anonymously) and deploys it asdeploywould from the app's directory, with nothing to clone or build (step app.deploy"Deploying <app> <version> from github.com/every-app/<repo> (release <tag>) to …", then the deploy's own steps;donewithdata.url,data.liveanddata.next; the same command later updates the app). The release path is the only one here: never clone a repository and deploy it, never build one from its URL. What an install can end on:
NO_RELEASE(exit 1): the repository publishes no release yet. Say which app, and go on to the next one; itsdata.fix(clone,pnpm install, then deploy from the clone) is for when the person asks for that, not a step of yours.R2_NOT_ENABLED(exit 1): OpenSEO, OpenCRM and OpenMail each bind an R2 bucket named after their Worker, the install makes it, and an account that never enabled R2 can't have one made by the API (OpenAnalytics binds none and never stops here). Once for the account, the person enables R2 in the dashboard: https://dash.cloudflare.com/?to=/:account/r2/overview, R2 Object Storage → Get started (free until something is stored; no bucket to make by hand;data.fixsays the same). Wait for them, then rundata.rerun(this same command); the other two won't stop on it again.warning APP_NEEDS_SETUPatstep app.secrets("<app> won't open until <names> is set",data.setup), after the upload: the app is live and shows Needs setup on the Gateway until its keys are set, anddone.data.nextcarrieseveryapp configure <app id>, the one command that opens it. Tell the person which keys it wants, by name (the warning's names;everyapp status --jsondata.apps[].setup.unmet[].names, the id asstatuslists it:openseo,crm,analyticsoropenmail; oreveryapp secret list <app id> --jsondata.app.setup.unmet), and stop there: the keys are never asked for here. When they want that app, each unmet setting'ssetupnames its file in these guides (dataforseo.md, openrouter.md, context.md, google.md, …), which takes over from this one;everyapp configure <app id>at their terminal sets every key the app needs in one go (the setup rules' first way). An app that stops on neither opens atdata.url; one the person skipped installs the same way later.
Verify
everyapp status --json:donewithdata.installed: true,data.claimed: true,data.owner.email,data.gatewayUrl=https://<gateway host>,data.appBaseDomain(=<app base domain>),data.mode: "self_hosted",data.tls: true(null: Cloudflare couldn't list the certificates, and aCERTIFICATES_UNKNOWNwarning says so; rerun status later),data.certificates[]eachready,data.access({ on }; null on a hosted Gateway, before the first upload, or when cf couldn't list the Worker's settings, which adds anACCESS_UNKNOWNwarning),data.apps[](one per registered app; itssetupis null when the app declares nothing to set or isn't up yet, else{ ready, settings, unmet, groups, command }:unmetthe settings that keep the app closed, each withnames,holder,need.kindand its guidesetup,setup/<name>.mdin these guides;command,everyapp configure <app id>, null onceready), anddata.fixes(each{ code, severity, message, run, note?, fix? };severityiserror,warning,infoorsettling) empty or only codes that don't mean the Gateway is unfinished:CERT_PENDING,DEPLOY_RUNNING,CERTIFICATES_UNKNOWN,ACCESS_UNKNOWN,APP_BINDING(an app settling, not the Gateway),APP_NEEDS_SETUP(warning, "<app> needs setup": that app'ssetup.readyis false; itsrunissetup.command.command, and the person's report printsGuide: https://everyapp.com/docs/agent/setup/<name>.mdunder it: that integration's file, not this one),SETTINGS_UNKNOWN(warning: cf couldn't list a Worker's secrets, so the settings it holds readset: null, which never blocks; run status again) andGATEWAY_OUTDATEDwithseverity: "info"(listed last: "Your every-app folder has a newer Gateway than the one online"; itsruniseveryapp update, and onlyupdatebrings it; the summary still says "nothing to fix", so go on).GATEWAY_OUTDATEDwithseverity: "error"("The Gateway is older than this everyapp") is not done: runeveryapp update, since every other command refuses the Gateway until then. Fix codes that mean this guide isn't done:GATEWAY_NOT_INSTALLED,GATEWAY_NOT_UPLOADED,NO_OWNER(run setup again),CHECKOUT_BEHIND(git pull, thenpnpm install),GATEWAY_CHANGEDandBINDINGS_DIFFER(setup repairs; the first asks at a terminal). The otherAPP_*andEMAIL_*codes belong to app deploys, not here. Every other setup file relies on this check.everyapp secret list --jsonfrom the checkout root:data.gateway.workerNameisevery-app-gatewayanddata.gateway.namesholdsGATEWAY_URL,CLOUDFLARE_ACCOUNT_ID,GATEWAY_DEPLOYMENT_MODE,BETTER_AUTH_SECRET,JWT_PRIVATE_KEY,JWT_PUBLIC_KEY;CF_DEPLOY_TOKENtoo after Step 9. From the root the event also carriesdata.apps[],{ id, setup }for each deployed app that declares something (the samesetupas status). One app's, from anywhere once it is deployed:everyapp secret list <app id> --json, whosedata.app.setupis thatsetupand whosedata.gateway.neededanddata.gateway.missingstill say the same of the Gateway's names (APP_NOT_FOUNDbefore its first deploy: in its directory instead).- Smoke check, from your shell:
curl -sS -o /dev/null -w '%{http_code}\n' https://<gateway host>/answers200(the page loads and the browser does the sign-in redirect; read from the code, not checked live) or a3xxto acloudflareaccess.compage (Cloudflare Access is in front; setup's own probe reads it the same way), not a TLS or name error;000means DNS or the certificate isn't ready yet, give it ten minutes.cf auth whoamisaysauthenticated: truewith the person's email. - The person opens
https://<gateway host>, signs in with the owner email and password, andhttps://<gateway host>/adminopens (Users, invitations, apps). - Logs:
cfhas no Worker tail (Cloudflare's cf↔wrangler reference: "cf cannot stream live logs yet"; live logs arenpx wrangler tail every-app-gateway, and wrangler has its own one-time login the person approves). In the dashboard, the setup rules' path: Workers & Pages →every-app-gateway→ Observability (Logs on older accounts) → Live; Workers Logs are on for the Gateway, so the same page keeps them.everyapp status --jsonis faster for everything setup touches.
If it fails
| error or symptom | meaning | what to do |
|---|---|---|
UNSUPPORTED_RUNTIME, NODE_TOO_OLD |
Node below 22.18.0, Bun, or a Volta/pnpm pin on an older Node | install Node 22.18.0 or newer (22, 24 or 26), open a new terminal, rerun; raise the pin |
cloudflare_login repeats, CLOUDFLARE_LOGIN_FAILED |
the device login wasn't approved within 5 minutes (cf's limit), or failed | everyapp login --json again, and hand the link and code over at once; a 403 managed challenge on a server: use CLOUDFLARE_API_TOKEN |
CLOUDFLARE_TOKEN_REJECTED |
CLOUDFLARE_API_TOKEN is set but mistyped, expired or short of permissions |
fix the token (Step 5's list), or unset CLOUDFLARE_API_TOKEN and log in |
choose_account |
several accounts (or several Gateways) and no --account-id |
ask the person; run the chosen data.choices[].rerun (--json always needs it; only a person at a terminal has their pick remembered) |
ACCOUNT_NOT_ACCESSIBLE, ACCOUNT_REQUIRED |
--account-id names an account this login can't use; a token lists none |
pick an id from the message; the id is in the dashboard URL, dash.cloudflare.com/<account id>/… |
GATEWAY_NOT_FOUND |
the named account has no Gateway, or the login lists no accounts | drop --account-id/CLOUDFLARE_ACCOUNT_ID, log in to the right account, or run setup to install |
upgrade_workers_paid, "Subscribe this account to Workers Paid" ("It's on the Free plan", or "Cloudflare refused the upload on the Free plan") |
Free plan; nothing created | Step 2, rerun |
CF_EMAIL_UNVERIFIED (Cloudflare API code 10034) |
the account's email isn't verified, so Cloudflare creates nothing | the person opens data.url (https://dash.cloudflare.com/profile) and finishes Step 1; run data.rerun |
DOMAIN_REQUIRED |
setup ran unattended without --domain |
one zone: run data.rerun (--domain apps.<zone>; another name under it works too); several: ask the person which data.choices[].label, run its rerun |
WORKERS_PAID_UNVERIFIED (warning) |
the subscription list didn't name the plan | ask the person to check Billing → Subscriptions; the run continues |
add_domain "Add a domain" (no --domain), ZONE_NOT_FOUND |
no domain in this account, or --domain isn't under one (wrong account?) |
Step 3; check cf auth whoami shows the intended account |
add_domain "Finish activating" |
nameservers not switched yet | finish Step 3's nameserver change, wait for the active email |
DNS_RECORD_CONFLICT |
a record already answers apps.<your domain> |
the person deletes it (domain → DNS) or picks another name; --force-dns only when they say so |
DNS_UNREADABLE |
the API token can't read the zone's DNS, so it can't tell whether a record is in the way | give the token Zone › DNS › Read, rerun; --force-dns only when they say so |
register_workers_subdomain |
first Worker in this account; the database and KV namespace exist already | open data.url, pick any name (no name field: Workers & Pages → Change next to Your subdomain), rerun: it resumes |
rerun_interactive (data.needsTerminal: true) |
the Gateway Worker serves a version everyapp didn't deploy | the person runs data.rerun in their own terminal and confirms; never rerun it yourself, with or without --json |
GATEWAY_OUTDATED from status (info) |
this checkout's Gateway is newer than the live one; the schema is current, nothing is broken | go on; everyapp update when the person wants it updated now |
GATEWAY_OUTDATED from status (error) or from any other command |
the Gateway's database lacks the migration this everyapp needs; other commands refuse it | everyapp update, then go on |
DOMAIN_CHANGE_UNSUPPORTED |
the Gateway lives elsewhere; it can't move | rerun without --domain |
claim_owner after 15 minutes |
nobody opened the link | they open data.setupUrl now, or rerun for a new link |
| "Use the newer setup link", the page refuses the link | expired, or replaced by a later run (maybe on another machine) | rerun setup; use only the newest link |
| "Password must be at least 8 characters", "Passwords do not match" | the claim form's own checks | fix the fields, submit again |
GATEWAY_UNREACHABLE, CERT_PENDING, curl answers 000 |
certificate or DNS still on the way | wait up to ten minutes; the link works as soon as the host answers |
OWNER_NOT_CLAIMED (warning, exit 0; data.needsTerminal: true) |
CI is set, or output is piped without --json: no link is shown there |
run setup at a terminal, or with --json from an agent on the person's machine |
SECRET_NOT_SETTABLE |
secret set on GATEWAY_URL, JWT_*, … |
setup owns those; nothing to set |
SETTING_VALUE_INVALID with data.name |
BROWSER_APP_INSTALLS takes exactly on or off (a plain setting; nothing was written) |
pipe the value data.fix describes |
SECRET_VALUE_REQUIRED, SECRET_SEVERAL_NAMES |
secret set CF_DEPLOY_TOKEN --json got no value, or a pipe with several names |
one of the setup rules' three ways, one name per pipe |
CONFIRMATION_REQUIRED |
secret unset … --json without --yes: nothing was removed |
ask the person; rerun with --yes only when they said so |
integration_not_configured, ai_provider_not_configured from an app |
the Gateway is fine; that integration's setting isn't set | the integration's own file in these guides |
an app's host answers 503 app_needs_setup, or a browser lands on https://<gateway host>/?setup=<app id> |
the Gateway keeps an app closed until its required settings are set; status names them under APP_NEEDS_SETUP |
the file its Guide: line names |
Rerun, rotate, remove
- Rerun
everyapp setup --domain apps.<your domain> --jsonany time: it logs in, finds the Gateway, upgrades it after agit pull(everyapp update --jsondoes only that), repairs bindings and hostnames, mints a new owner link while there is no owner, and otherwise ends "up to date" (data.gateway: "up-to-date").--domaincan be left off once installed; the address and the mode can't change. - Rotate the Cloudflare login:
cf auth logout, theneveryapp login --json. The owner's password: Forgot password? on the sign-in page when the Gateway sends email, else an admin's Users → ⋯ → Password reset link (valid an hour). The Gateway's own keys: docs/runbooks/key-rotation.md: a new pair in the dashboard, the public key recorded in the install row,setup --redeployat a terminal, theneveryapp deployfrom every app's directory (apps keep the old key and answer 401 until then).CF_DEPLOY_TOKEN: roll the token at https://dash.cloudflare.com/profile/api-tokens (⋯ → Roll → Confirm: same permissions, new secret, the old one stops at once), then set the new value one of the three ways; or create a new token, set it, then delete the old one. - Remove Gateway app installs:
everyapp deploy-token remove --json(both settings; under--jsonit needs--yes, elseCONFIRMATION_REQUIRED; add it only when the person said so), then delete the token in the dashboard; or keep the token and turn installs off:BROWSER_APP_INSTALLS=off everyapp secret set BROWSER_APP_INSTALLS --from-env BROWSER_APP_INSTALLS --json(offis a toggle, not a secret: the one value these guides put on a command line, as docs/runbooks/gateway-app-installs.md does). Removing the Gateway itself is the person's, in the dashboard, and never yours to start (deleteremoves an app;teardownempties a dedicated test account the person names, never one in use: the safety rules on setup). Setup finds a Gateway only by its D1 databaseevery-app-gateway: while that database exists, a rerun repairs the Gateway it describes (a Worker deleted by hand comes back at the same address); once it is gone, setup installs fresh, asking first when a Workerevery-app-gatewayis still in the account ("Replace it with <version>?"; unattended,rerun_interactive).
Optional: MCP servers worth having beside EveryApp
Not part of setup, and never added by you unasked. Two are worth adding; nothing else is. When the person's task needs one, offer it in one line ("Cloudflare's MCP server would let me read this account's DNS and Access settings from here. Add it to Claude Code?") and do nothing until they say yes.
The Gateway's own connector, first. https://<gateway host>/mcp is
every app the person can open, as tools, behind the Gateway's sign-in and
consent; tools has the commands per client. Every member can
have it. The one below is for the owner or operator, the person whose
Cloudflare login ran everyapp setup.
Cloudflare's MCP server, https://mcp.cloudflare.com/mcp. The whole
Cloudflare API and its docs behind four tools: search and execute (the
agent writes JavaScript against the API; it runs on Cloudflare's side),
docs and whoami. Connecting opens Cloudflare's login and a consent
page: choose Minimal; a call that needs more asks for that scope then
(step-up, since 2026-10-19). It then acts as that person on every account
their login reaches.
- Good for, with a Gateway: reading the account the Gateway runs in when
everyapp statusandeveryapp access showdon't answer the question. The zone and its DNS records (<gateway host>and one<app>.<app base domain>per app, each a Workers Custom Domain with its own record), the Workers (every-app-gatewayand each app's) with their versions, the D1 databaseevery-app-gateway, the Access team, applications and policies thateveryapp access onmade, Email Routing, Workers logs, the plan and the bill. "Which version is serving", "is the DNS record there", "why does this app host 503", "who does the Access policy let in", "what did October cost". - Noise for: building and deploying.
everyapp setup,deploy,update,install,secret set,access on,emailandmcp onwrite the Gateway's registry and the deploy ledger as well as Cloudflare, and a deploy refuses to replace a Worker last changed outside everyapp (assertReplaceableinpackages/cli/src/lib/cf/deploy.ts: the nexteveryapp deploystops and asks a person to confirm in a terminal). A Worker, secret, DNS record or Access policy changed through this server is oneeveryappdoesn't know about. Look with it; change witheveryapp. - The rule: read before write. A write through it is a live API call on
the whole account, as the person, with no undo. It goes in the person's
own client config (Claude Code's default
localscope or--scope user,~/.cursor/mcp.json,~/.codex/config.toml), never in one the repository shares (.mcp.jsonfrom--scope project,.cursor/mcp.json,.codex/config.toml), where every clone would be offered the account.
When they say yes, by client. The sign-in is the person's step every time: the consent page is theirs, never yours.
Claude Code. You may run it:
claude mcp add --transport http cloudflare https://mcp.cloudflare.com/mcpThen the person types
/mcpin Claude Code, chooses cloudflare, Authenticate, and allows in the browser.Codex. You run
codex mcp add cloudflare --url https://mcp.cloudflare.com/mcp; the person runscodex mcp login cloudflarein their own terminal.claude.ai and Claude Desktop: Settings → Connectors → Add custom connector, the URL above.
Cursor: in
~/.cursor/mcp.json,{ "mcpServers": { "cloudflare": { "url": "https://mcp.cloudflare.com/mcp" } } }, then sign in when Cursor asks.
Removing it: claude mcp remove cloudflare, codex mcp remove cloudflare,
or delete the entry.
GitHub. everyapp install <github url> downloads a public release
anonymously and needs no GitHub access. For what surrounds an app's
repository (which release, its README, an issue, the Actions runs of the
starter's security-review.yml), a harness with a shell has gh; in
claude.ai, turn on the GitHub connector (Settings → Connectors).
Nothing to add.
Not worth adding. Cloudflare's separate docs server
(https://docs.mcp.cloudflare.com/mcp): the API server's docs tool is
the same search. A docs server for EveryApp: none; the agent docs are
Markdown at https://everyapp.com/docs/agent/<page>.md and
https://everyapp.com/llms.txt, which any client reads without MCP.
Sources
Verified 2026-10-23; CLI shapes re-checked after #400, #401 and #410 merged; data.apps[].setup, APP_NEEDS_SETUP, SETTINGS_UNKNOWN and secret list <app id> after #383, #395, #396 and #397 (2026-10-24). The agent docs' version of this page: landing-page/content/agent/setup.md in the checkout, served at https://everyapp.com/docs/agent/setup.md once the site deploys with the launch.
- Plans and prices: https://developers.cloudflare.com/workers/platform/pricing/, https://developers.cloudflare.com/d1/platform/pricing/, https://developers.cloudflare.com/registrar/faq/ (at cost), https://costgoat.com/pricing/cloudflare-domains (third party, 2026-10-20)
- Account, billing, domains: https://developers.cloudflare.com/fundamentals/account/create-account/, https://developers.cloudflare.com/fundamentals/user-profiles/verify-email-address/, https://developers.cloudflare.com/fundamentals/subscriptions-and-billing/create-billing-profile/, https://developers.cloudflare.com/registrar/get-started/register-domain/, https://developers.cloudflare.com/fundamentals/manage-domains/add-site/
cfand tokens: https://developers.cloudflare.com/cf/get-started/, https://developers.cloudflare.com/cf/ci/, cf 1.0.0-beta.14's source (Math.min(expires_in, 300): the 5-minute login), https://github.com/cloudflare/cf/issues/23 (the 403 from datacenter IPs), https://developers.cloudflare.com/api/resources/zones/methods/list/ and https://developers.cloudflare.com/fundamentals/api/reference/permissions/ (Zone Read and DNS Read are distinct), https://developers.cloudflare.com/fundamentals/api/how-to/roll-token/- workers.dev, logs, Node: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/, https://github.com/cloudflare/workers-sdk/issues/9045, https://developers.cloudflare.com/workers/observability/logs/workers-logs/, https://developers.cloudflare.com/cf/wrangler/reference/, https://nodejs.org/en/about/previous-releases
- The four apps (Step 11), verified 2026-10-25: landing-page/content/docs/setup-prompt.txt (step 4 and its "Only I can" list; #595, #607), docs/runbooks/app-releases.md (#611:
pnpm pack-for-app, the Release workflow, which apps bind R2), landing-page/content/agent/deploy.md ("Releases") and landing-page/content/agent/errors.md (NO_RELEASE,R2_NOT_ENABLED), packages/cli/src/commands/install/index.ts, packages/cli/src/lib/app/release.ts, packages/perimeter/src/install/resources.ts, https://developers.cloudflare.com/r2/get-started/ (the dashboard link) - In this repository: packages/cli/src/commands/setup/{runSetup,deployGateway,index,command}.ts, commands/status/{index,problems,render}.ts, lib/app/settings.ts and packages/sdk/src/internal/appSettings.ts (the settings model), apps/every-app-gateway/src/server/app-needs-setup.ts (the closed app), commands/update/command.ts, commands/secret/index.ts, commands/configure/index.ts, commands/email/index.ts, lib/cf/{account,plan,deploy,errors}.ts, lib/domains/{preflight,zones}.ts, lib/gateway/{discover,connect,setup-link,apply,resources}.ts, lib/access/settings.ts, lib/errors.ts, lib/output/{events,report,command}.ts, lib/runtime.ts, apps/every-app-gateway/src/routes/{__root,setup}.tsx, docs/runbooks/{key-rotation,gateway-app-installs}.md, landing-page/content/docs/{cloudflare,domain,gateway,troubleshooting}.mdx
Setup guides: the index and their rules
The index of the step-by-step setup guides (one per integration the Gateway holds) and the rules every guide follows: the three ways a value goes in, never through the chat, how an agent finds which guide applies.
Cloudflare Access for the Gateway
Step-by-step guide: turn Cloudflare Access on in front of the Gateway and every app (the Zero Trust team, the one-time PIN, Sign in with Cloudflare), check it, recover from a lockout, turn it off.