Step-by-step guide: a Workers AI token (WORKERS_AI_API_TOKEN) on the Gateway for apps that list workers-ai: the token, the account, the check, rotation.
Workers AI runs Meta's Llama and other open models inside the Gateway's own
Cloudflare account, behind the Gateway's AI proxy. openmail lists it in an
optional feature's providers beside a required openrouter, so secret list shows the
token in settings with set: false, never in unmet: the inbox opens
without it. Its Worker calls Workers AI (the Email Agent, the draft verifier
and the prompt-injection scanner, on /api/ai/workers-ai/v1/chat/completions)
only when built with AI_PROVIDER=workers-ai, which is still the build
default until the inbox's PR #6 flips it to OpenRouter; a Worker built with
it and no token says so in the chat. Only the token is missing: the account is
CLOUDFLARE_ACCOUNT_ID, which everyapp setup set. At the end the person
has one Cloudflare API token limited to Workers AI on that account, held by
the Gateway, and a Workers AI model answering through it.
optional for openmail (in an optional feature's providers: ["workers-ai"]); needed only by a Worker built with AI_PROVIDER=workers-ai
A Cloudflare API token with Workers AI Read on the Gateway's account (and Edit when made by hand: Cloudflare's REST guide asks for both on a custom token): the token string alone, one line, no Bearer. New user tokens start cfut_, account tokens cfat_.
CLOUDFLARE_ACCOUNT_ID
set once by everyapp setup; secret set refuses it (SECRET_NOT_SETTABLE from the every-app checkout, SECRET_NOT_DECLARED from an app directory)
The account inference runs and bills in: data.accountId in pnpm everyapp status --json, written <account id> below. The token must include this account.
OPENROUTER_API_KEY stays: the inbox's manifest requires it, and OpenSEO and Open CRM use it. This guide adds a provider; it removes none.
As of October 2026 (Cloudflare's pricing page, updated 2026-10-20):
The Gateway's account is on Workers Paid already ($5 a month; everyapp needs it). Workers AI adds no fixed fee, and an API token is free.
10,000 neurons a day are included. Beyond that, Workers Paid bills $0.011 per 1,000 neurons on the monthly invoice; Workers Free stops for the day instead (error 3036).
The Workers AI path's default agent model (and its draft verifier), @cf/meta/llama-4-scout-17b-16e-instruct: $0.27 per million input tokens, $0.85 per million output; 300 requests a minute.
@cf/moonshotai/kimi-k2.6 (the inbox's optional AGENT_MODEL): paid-only; $0.95 in and $4.00 out per million tokens; 20 requests a minute per account and model on Workers Paid (Cloudflare's 50-a-minute tier is for calls through an AI Gateway on prepaid credits, which the Gateway doesn't use), shared by every organization on the Gateway.
Sign in to the Cloudflare dashboard and create the token. cf's OAuth login cannot mint one: cf auth whoami lists no API-token scope, and the token endpoints answer it 403 code 9109. So neither cf accounts tokens create nor the API works from the login; it needs a token to make a token.
Copy the token off the screen (shown once) and type or export it in their own terminal.
Choose a user token (tied to their login) or an account token (a team's choice; needs API Token Provisioning or Super Administrator on the account), and whether it expires.
pnpm everyapp secret list openmail --json, from any directory: the WORKERS_AI_API_TOKEN entry in data.app.setup.settings (names["WORKERS_AI_API_TOKEN"], need.kindoptional, holdergateway, setupsetup/workers-ai.md) with "set": false means this guide applies. It is never in unmet (command, pnpm everyapp configure openmail, names it under its feature, with pnpm everyapp provider add workers-ai) and the inbox opens without it; what keeps the inbox closed is OPENROUTER_API_KEY (openrouter.md) and its email sender (email.md). "set": true: ask before replacing (a value can't be read back; replacing means a new token). In the older fields data.gateway.needed has it with "required": false, never in data.gateway.missing. status prints no Guide: line for it (only unmet settings get one): secret list --json, carrying the path as the entry's setup, is how an agent lands here (in the checkout: landing-page/content/agent/setup/workers-ai.md). APP_NOT_FOUND ("openmail isn't deployed to this Gateway yet"): deploy the app first, from its directory (the token can be set before that all the same, from anywhere).
pnpm everyapp status --json: data.installed and data.claimed true; data.accountId is the account the token must include (tell the person; it is not a secret); data.apps[] has "id": "openmail" with "status": "active", "bound": true and the same setup; while a required setting is missing data.fixes has APP_NEEDS_SETUP for it (severity warning, headline "openmail needs setup", run the same command). action_required cloudflare_login with waiting: true (status logs in by itself): show data.url and data.code to the person and keep reading; pnpm everyapp login --json is only for logging in ahead of time.
In 1's output (or pnpm everyapp secret list --json from any directory), data.gateway.names has CLOUDFLARE_ACCOUNT_ID. Missing: stop and tell the person. It is written once, at install, from the account the Gateway lives in (data.accountId is what it should be); secret set refuses it and setup adds nothing to a Gateway that already has a version, so there is no everyapp repair today. The settings model leaves it out (an install-owned setting), so the setup gate doesn't notice it missing: the proxy's 503 (If it fails) is where it shows.
Ask, together with 5 in one message, and wait for both answers before Steps (they change the steps): was the inbox's Worker deployed with AI_PROVIDER=workers-ai (a variable of its own deploy; status doesn't show it)? It decides whether the inbox needs this token at all: the manifest marks it optional, so the inbox opens either way. The build default is still workers-ai (the inbox's PR #6 flips it to OpenRouter; open): a Worker deployed without the variable uses this token; one deployed with AI_PROVIDER=openrouter never does, until its own Worker is deployed again without it, from its repository (its cloudflare.config.ts reads it), not from here: say so, and never redeploy the inbox from this guide.
Ask whether the person already holds a Cloudflare API token with Workers AI on this account (a token from another project is fine if it includes this account). Yes: skip to step 3.
One step per message, and wait between them: step 1 once the two answers from Before you start are in; step 2, then "copied"; step 3, then "done". The curl line comes in Verify, after that.
You: Say which token to make. A user token (My Profile) is the quick path for a solo operator; an account token keeps working after that person leaves and is the better fit for a team. Both reach Workers AI the same way: the Gateway sends either as a Bearer token to api.cloudflare.com/client/v4/accounts/<account id>/ai/v1/…, and Cloudflare's compatibility matrix lists Workers AI as supporting account tokens (not tried live here). Give them data.accountId to check against the token's account resources.
Person: Create the token. Done when the screen shows the token once, with a copy button and a curl … /user/tokens/verify test under it. Wait for "copied".
Prefilled user token: open https://dash.cloudflare.com/?to=/:account/ai/workers-ai (the Workers AI page; pick the Gateway's account if asked), select Use REST API, then Create a Workers AI API Token. A token form opens prefilled (Cloudflare's guide says it grants Workers AI Read on this account, which is what the /ai/* endpoints need; one permission row is expected): check that Account Resources names the Gateway's account (data.accountId), leave TTL (time to live) empty unless they want an expiry (the Gateway stops the day it expires), select Create API Token, then Copy API Token.
Custom user token: https://dash.cloudflare.com/profile/api-tokens, Create Token, the custom token at the bottom (label unverified), name it every-app-gateway workers-ai, permissions Account | Workers AI | Read and a second row Account | Workers AI | Edit (Cloudflare's REST guide asks for both on a token made by hand), account resources: Include, the Gateway's account (data.accountId), no client IP filter (the Gateway's egress addresses vary), Continue to summary, Create Token.
Account token: in the dashboard pick the Gateway's account, Manage account > Account API tokens, Create Token, the same name and permissions, optional expiration, Continue to summary, Create Token.
Person: Hand it to the Gateway (the setup rules' third way, the hidden read, in their own terminal, in the every-app folder; bash or zsh):
After the first line the terminal shows nothing, not even a prompt: paste the token, press Enter, then run the second line. SECRET_EMPTY means the paste didn't land: redo the first line in the same terminal. fish: read -s -x WORKERS_AI_API_TOKEN (it does show read> ), then the same pnpm everyapp secret set … --from-env … line; set -e WORKERS_AI_API_TOKEN clears it afterwards.
The last line is "type": "done"; the change is live at once (one new Gateway version, no deploy). pnpm everyapp configure openmail --from-env --json sets it from the same variable (and every other unset setting of the inbox whose variable is exported; its done event's data.set names them). Keep the variable until the smoke check below is done, then unset WORKERS_AI_API_TOKEN. Without the smoke check, the setup rules' first way is simpler: in a second terminal, pnpm everyapp configure openmail (a feature's Gateway setting isn't asked for there: it names pnpm everyapp provider add workers-ai; once the inbox is ready its menu lists WORKERS_AI_API_TOKEN) or pnpm everyapp secret set WORKERS_AI_API_TOKEN for just this one; paste at the hidden prompt. Wait for "done".
You: Run the Verify section. The person runs its curl line in the terminal that still has the variable.
pnpm everyapp secret list openmail --json, from anywhere: the WORKERS_AI_API_TOKEN entry in data.app.setup.settings has "set": true (it was never in data.app.setup.unmet: optional); data.app.setup.ready doesn't depend on it (OPENROUTER_API_KEY, required by the same manifest, and its email sender do: until then command and data.next name what is left). In the older fields, its data.gateway.needed entry has "set": true, data.gateway.missing never listed it, and data.gateway.names does (for an inbox not deployed yet, the root pnpm everyapp secret list --json shows data.gateway.names and that is the check). Then pnpm everyapp status --json: the openmail entry's setup.ready is true and data.fixes has no APP_NEEDS_SETUP for it, once everything required is set.
Smoke check, in the person's terminal with WORKERS_AI_API_TOKEN exported. The header goes in on stdin (-H @-) so the token never sits in curl's argument list, which the process list shows. The token reaches Workers AI on the Gateway's account (a few neurons):
expects a JSON body with choices[0].message.content. Any "success":false body is read in If it fails; a 401 starts with the verify curl there. Then unset WORKERS_AI_API_TOKEN.
The app, only when the inbox's Worker was built with AI_PROVIDER=workers-ai (the build default until the inbox's PR #6; Before you start, 4); one built with AI_PROVIDER=openrouter never sends this token and the curl line above is the whole proof. Either way the inbox opens only once every required setting is set (Verify 1): a browser sent to https://<gateway host>/?setup=openmail means something still isn't. Open https://openmail.<app base domain>/, open a mailbox, Show agent panel (the header button), type in "Ask your email agent..." and send. A reply streams in, and the Gateway's ai.proxy.usage line (step 4) says "provider":"workers-ai". A missing token shows up in the panel as "AI isn't set up on this gateway yet, so I can't help with this inbox. An admin sets WORKERS_AI_API_TOKEN with pnpm everyapp secret set WORKERS_AI_API_TOKEN."; any other refusal as the Gateway's message.
The Gateway's logs, when a refusal isn't clear: cf has no terminal tail (cf workers lists none; cf o11y telemetry live-tail only prepares a websocket, not tried here; cf o11y telemetry query is the raw query API). Cloudflare's CLI for it is npx wrangler tail every-app-gateway --format json (after its own wrangler login; wrangler isn't in this checkout). Dashboard, the setup rules' path: https://dash.cloudflare.com/?to=/:account/workers-and-pages, then Workers & Pages → every-app-gateway → Observability (Logs on older accounts) → Live. The proxy writes one JSON line per call: "evt":"ai.proxy.usage" with provider, model, status and usage; "evt":"ai.proxy.upstream_failed" with the message when the request to Cloudflare failed. The Gateway's config turns these logs on; Workers Paid keeps 7 days.
An account token (cfat_): the same header on https://api.cloudflare.com/client/v4/accounts/<account id>/tokens/verify; on the user route it answers 401 code 1000 "Invalid API Token", like any token that route doesn't know. "success":true with "status":"active": the string is good, and the rows for code 10000 and 9109 say what is (the permission or the account). 401 code 1000 on its own route: the string (its row).
Error or symptom
Meaning
What to do
a browser opening the inbox is sent to https://<gateway host>/?setup=openmail; any other request gets 503 with the body {"error":"app_needs_setup","app":"openmail"}
the Gateway's setup gate: a required setting of the inbox is still unset (OPENROUTER_API_KEY or its email sender; this token is optional and never closes the inbox)
data.app.setup.command, pnpm everyapp configure openmail, then Verify 1
503 ai_provider_not_configured, "needs WORKERS_AI_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
the Gateway holds no token (the inbox opens anyway: its manifest lists the provider in an optional feature; a Worker built with AI_PROVIDER=workers-ai shows it in the chat as "AI isn't set up on this gateway yet, so I can't help with this inbox. An admin sets WORKERS_AI_API_TOKEN with pnpm everyapp secret set WORKERS_AI_API_TOKEN."), or no account id (which the gate doesn't check)
secret list --json; set the token; CLOUDFLARE_ACCOUNT_ID missing: no everyapp repair today, tell the person (Before you start, 3)
provider_not_allowed
the app's manifest doesn't list workers-ai under providers
openmail lists it; another app adds it to everyapp.config.ts and deploys
model_not_allowed
a model id outside the app's ai.models
use one from the manifest, or add it and deploy
ai_route_not_allowed
a path other than /v1/chat/completions, /v1/embeddings, /v1/responses (the native run route is closed)
call the OpenAI-compatible routes
502 ai_upstream_failed
the request to api.cloudflare.com failed before an answer
retry; the Gateway log line ai.proxy.upstream_failed has the message
401, code 1000 "Invalid API Token" (the verify routes)
the string is wrong, expired, rolled or deleted; or a cfat_ token on the user route
verify a cfat_ token on /accounts/<account id>/tokens/verify; otherwise make a new token and set it again
401, code 10000 "Authentication error" (the /ai/v1 route: curl's chat completion, or passed through by the Gateway)
token invalid, expired, rolled, deleted, IP-filtered, or without Workers AI on this account; or $WORKERS_AI_API_TOKEN empty in that terminal
the verify curl above; make a new token with Workers AI Read including data.accountId; set it again
403, code 9109 "Unauthorized to access requested resource" or "Invalid access token"
the token's resources don't include this account
a token whose account resources include the Gateway's account
403, code 5035 "This model requires a Workers Paid plan."
a paid-only model (kimi-k2.6) on an account without Workers Paid
the Gateway's account must be on Workers Paid (everyapp needs it anyway)
429, code 3036 "You have used up your daily free allocation of 10,000 neurons."
Workers Free's daily cap
Workers Paid; it doesn't occur there
429, code 3040 "Capacity temporarily exceeded", or a bare 429
Cloudflare capacity, or a rate limit (kimi: 20 a minute per account; the limit's own code unverified)
retry later; on kimi expect it, or use @cf/meta/llama-4-scout-17b-16e-instruct
400 code 5007 "No such model", or 404 code 3042 "The model name is invalid"
the model id isn't in the catalog
cf ai models list --search llama (read-only) for a current id; change the app's manifest and code
SECRET_EMPTY
$WORKERS_AI_API_TOKEN is empty in the terminal that ran secret set
redo the read -rs …; export … line in that same terminal, then rerun
SECRET_VALUE_REQUIRED
nothing was piped and there is no terminal
one of the setup rules' three ways
SECRET_VALUE_REQUIRED from pnpm everyapp configure openmail --json
nothing was piped (with --from-env, stdin isn't read), no variable of a missing name was found, and there is no terminal; the message names what the inbox still needs (this token is optional, so never among them)
the person's terminal (step 3), NAME=value lines on stdin, or the hidden read then its data.rerun, pnpm everyapp configure openmail --from-env
SECRET_SEVERAL_NAMES
--from-env or a pipe with several names
one name per call
SECRET_NOT_SETTABLE (from the every-app checkout) or SECRET_NOT_DECLARED (from an app directory)
the name is one setup writes (CLOUDFLARE_ACCOUNT_ID)
set only WORKERS_AI_API_TOKEN; never add it to the manifest's secrets; a wrong account id has no everyapp repair today (tell the person)
400, code 6003 "Invalid request headers" / 6111 "Invalid format for Authorization header" (the verify routes)
$WORKERS_AI_API_TOKEN is empty in that terminal (not exported, or a new terminal); the chat route shows the same as 401 code 10000
redo step 3 in one terminal, in order; an HTML page instead means a mistyped URL
Rerun: every step is safe to repeat. configure and secret set replace the value in one new Gateway version; a second token on Cloudflare's side is harmless until you delete it. Interrupted or back later: start at Verify and continue at the first step that isn't done.
Rotate: on the token's row in the dashboard (user tokens at https://dash.cloudflare.com/profile/api-tokens, account tokens under Manage account > Account API tokens) the three-dot menu, Roll, Confirm. The new secret keeps the permissions and the old one stops at once, so run step 3 again straight away. The API's roll: cf user tokens roll <token-id> for a user token, cf accounts tokens roll <token-id> for an account token; both need an API token with the matching API Tokens Edit permission, not the login.
Remove: pnpm everyapp secret unset WORKERS_AI_API_TOKEN --json (add --yes only when the person said so). The inbox keeps opening (its manifest marks the provider optional); a Worker built with AI_PROVIDER=workers-ai then loses its agent, verifier and scanner to ai_provider_not_configured, one built with AI_PROVIDER=openrouter is unaffected. Then delete the token in the same row menu (label unverified): a token the Gateway no longer holds is still a token.
In this repository: apps/every-app-gateway/src/server/ai-proxy.ts, apps/every-app-gateway/src/server/app-needs-setup.ts (the setup gate: the redirect to /?setup=<app id> and 503 app_needs_setup), packages/sdk/src/internal/aiProviders.ts (setup/workers-ai.md; CLOUDFLARE_ACCOUNT_ID under alsoNeeds), packages/sdk/src/internal/appSettings.ts (AppSetup: unmet, settings, features, command; a provider is required unless an optional feature lists it; providerNames leaves install-owned settings out), packages/sdk/src/internal/gatewaySettings.ts, packages/cli/src/lib/gateway/apply.ts (install-owned secrets go with the first deploy only), packages/cli/src/commands/secret/index.ts (secret list <app> from anywhere, data.app.setup, the kept data.gateway.needed and missing, APP_NOT_FOUND), packages/cli/src/commands/configure/index.ts (configure <app>: the feature prompts and the menu, --from-env), packages/cli/src/lib/app/settings.ts (the Guide: line), packages/cli/src/commands/status/problems.ts (APP_NEEDS_SETUP), packages/cli/src/lib/secretValue.ts, docs/capabilities.md; in openmail: everyapp.config.ts (openrouter required, workers-ai in an optional feature), cloudflare.config.ts (AI_PROVIDER defaults to workers-ai until its PR #6) and workers/everyapp/gateway.ts (each provider's models)