OpenRouter for the Gateway
Step-by-step guide: an OpenRouter key (OPENROUTER_API_KEY) on the Gateway, the model provider most apps use: the account, credits, the key, the check, rotation.
OpenRouter is one API key in front of many model providers (OpenAI,
Anthropic, Meta, Moonshot and others), prepaid in US dollars. The Gateway
holds the key as OPENROUTER_API_KEY and proxies /api/ai/openrouter/...
to openrouter.ai for every app that lists the openrouter provider, so no
app holds it. OpenSEO uses it for SAM (the in-app SEO agent), prompt
suggestions and website research. Open CRM uses it for its research agent
and web research (Perplexity's Sonar models). openmail uses it when
its Worker was deployed with AI_PROVIDER=openrouter (its
cloudflare.config.ts still defaults the variable to workers-ai until
the inbox's PR #6 merges; the other provider is
workers-ai.md). At
the end the person has an OpenRouter account with credits, one key with a
spending limit and no expiry, the key on the Gateway, and a model answering
inside the app.
Settings
| NAME | Required or optional | What it holds |
|---|---|---|
OPENROUTER_API_KEY |
required by every app whose manifest lists openrouter |
the API key exactly as OpenRouter showed it once at creation (sk-or-v1-..., one line) |
The Gateway sends the key only to https://openrouter.ai, and only on POST
to /api/v1/chat/completions, /api/v1/completions, /api/v1/responses
and /api/v1/embeddings.
Costs and plans
As of October 2026: an account is free; credits are prepaid in USD and pay
for inference at the providers' own prices, with no markup. Buying credits
costs a fee: 5.5% of the amount with a minimum fee of $0.80 by card or
AliPay, 5% flat in USDC; at least $5 per purchase, at most $25,000; the fee
is not refunded. OpenRouter may expire unused credits 365 days after
purchase. The models OpenSEO and the inbox use are all paid (per million tokens,
from the public model list on 2026-10-23): openai/gpt-5.6-luna $0.20 in /
$1.20 out, anthropic/claude-haiku-5.5 $0.10 / $0.50,
meta-llama/llama-4-scout $0.10 / $0.30, meta-llama/llama-3.1-8b-instruct
$0.05 / $0.08, moonshotai/kimi-k2.6 $0.65 / $3.41, minimax/minimax-m3
$0.30 / $1.20. Paid models have no platform request cap; a negative balance
makes every call fail. :free models are capped at 20 requests a minute and
50 a day (1,000 a day once $10 of credits were ever bought), and the apps
never ask for one. $10 is a sensible first top-up: the $0.80 minimum fee
makes $5 cost $5.80 and $10 cost $10.80, and either is enough to set up and
try the apps.
Only a person can
- Sign up or sign in at openrouter.ai and accept its terms.
- Buy credits (a card, AliPay or USDC) and decide how much.
- Choose the account's privacy settings (which providers may train on prompts; zero data retention per model group).
- Create the key, pick its spending limit and expiry, and copy it off the screen.
- Delete a key on OpenRouter's side.
Before you start
pnpm everyapp secret list openseo --json,pnpm everyapp secret list crm --jsonandpnpm everyapp secret list openmail --json, from any directory, one per app the person has (APP_NOT_FOUND, "<app> isn't deployed to this Gateway yet", means that app isn't deployed: the key can still be set now, from any directory, and the app uses it on its first deploy). TheOPENROUTER_API_KEYentry indata.app.setup.unmet(names,need.kindrequired,holdergateway,setupsetup/openrouter.md) means this guide applies, and the app doesn't open until it is set: the Gateway lists it as needing setup, sends a browser openinghttps://<app>.<app base domain>to its setup dialog and answers anything else 503app_needs_setup.data.app.setup.commandispnpm everyapp configure <app id>, which asks for every missing setting (data.nextrepeats it). The entry indata.app.setup.settingswith"set": truemeans it is set already: ask before replacing it; a value can't be read back, so replacing means a new key (see Rerun, rotate, remove). The older fields still say the same:data.gateway.needed[]has{ "name": "OPENROUTER_API_KEY", "required": true, "set": false }anddata.gateway.missingnames it while unset. This is how an agent lands here:status(under "<app> needs setup") andsecret list <app id>(under the unmet setting) printGuide: https://everyapp.com/docs/agent/setup/openrouter.md, andsecret list --jsoncarries the path as the entry'ssetup(in the checkout:landing-page/content/agent/setup/openrouter.md). openmail: its manifest requiresopenrouterand listsworkers-aiin an optional feature, soOPENROUTER_API_KEYis inunmetandWORKERS_AI_API_TOKENonly insettingswithset: false: the inbox opens with this key and its email sender (email.md); workers-ai.md only for a Worker built withAI_PROVIDER=workers-ai. An app that doesn't need OpenRouter: stop here.pnpm everyapp status --json: in thedoneevent,data.installedanddata.claimedare true,data.gatewayUrlishttps://<gateway host>, anddata.apps[]has an entry with"id": "openseo","id": "crm"or"id": "openmail"and"bound": true, carrying the samesetup; while a required setting is missingdata.fixeshasAPP_NEEDS_SETUPfor the app (severitywarning, headline "<app> needs setup",runthe same command). Not installed or no owner: gateway.md first.
Steps
- You: run the two checks above and tell the person what is set and
what is missing. Say what OpenRouter is for in one line (the model behind
SAM, the CRM's research agent, or the inbox agent). Ask whether they already have an OpenRouter
account or key: an account skips 2 (and 3 when it has credits); a key
whose value they still have (OpenRouter shows it once, at creation) goes
in with
--from-envfrom their shell (step 6's alternative) and skips 5, with two checks: the key OpenRouter made for them during onboarding has a $100 limit and expires after 180 days, and Verify 2 shows any key'sdata.expires_at. Then give steps 2-6 as one list, the default path only (the alternatives in 5 and 6 when the answer calls for them), and wait after 6. - Person: open https://openrouter.ai/sign-in. Sign in, or choose Sign Up and create an account: Google, GitHub, or an email address (a code or link is sent, then a password); a MetaMask wallet and passkeys are offered too. The page needs JavaScript. Done when the top-right menu shows their account.
- Person: open https://openrouter.ai/settings/credits. Click the button to add credits (its goal: top up the prepaid balance), enter an amount ($10 to start; $5 is the minimum), pay by card, AliPay or USDC. Done when the page shows the new balance. Without credits every call answers 402.
- Person (optional, read it with them): open https://openrouter.ai/settings/privacy. One set of toggles decides whether OpenRouter may route to providers that train on prompts, separately for paid and free models; a second set switches zero data retention on per model group (Anthropic, OpenAI, Google, all other models), which drops that group's first-party endpoints. Leaving training off is fine for the apps' models (unverified for every provider of each model; the public API doesn't show it). A model with no endpoint left under the policy fails with a 404 "0 endpoints out of N requested are available matching your guardrail restrictions and data policy ..." (older wording: "No endpoints found matching your data policy").
- Person: open https://openrouter.ai/settings/keys. Click Create
Key (label unverified; OpenRouter's docs name no button; its goal: a
new key for the Gateway). Name it
every-app gateway <gateway host>. Credit limit: an amount they are happy to lose, e.g. $20. Limit reset: monthly; without a reset the limit is for the key's lifetime, and a key that reaches it is refused until the limit is raised on the key's page or, with a reset, until it resets. Expiration: No expiration, or write the date down as the rotation deadline (an expired key fails with 401). Create. The key is shown once: copy it now and keep the tab open until step 6 is verified. Alternative, the setup rules' way two, when the person exported a management key (made at https://openrouter.ai/settings/management-keys) before starting you:curl -s -X POST https://openrouter.ai/api/v1/keys -H "Authorization: Bearer $OPENROUTER_MANAGEMENT_KEY" -H 'Content-Type: application/json' -d '{"name":"every-app gateway <gateway host>","limit":20,"limit_reset":"monthly"}' | jq -r '.key' | pnpm everyapp secret set OPENROUTER_API_KEY --jsoncreates the key and sets it without showing it (the response's top-levelkey;data.hashnames it later). A management key can create and delete every key and read spend, so prefer the console unless they already have one. - Person: the setup rules' way one, the person types it. In a second terminal, in
the
every-appfolder:pnpm everyapp configure <app id>(openseo,crmoropenmail: the app that needs it; the key is set once, for every app), which asks for this key among the app's other unset settings, orpnpm everyapp secret set OPENROUTER_API_KEYfor just this one. The prompt is hidden (OPENROUTER_API_KEY, under lines saying what it is for and where it comes from;secret setasksValue for OPENROUTER_API_KEY); paste, Enter. It printsSet OPENROUTER_API_KEY on the Gateway. Then they tell you. Alternative, the setup rules' way three, when the key is in a variable the person exported before starting you:pnpm everyapp configure <app id> --from-env --json(thedoneevent'sdata.setholdsOPENROUTER_API_KEY;data.changed.gateway.versionsmaps the Gateway Worker to its new version id) orpnpm everyapp secret set OPENROUTER_API_KEY --from-env OPENROUTER_API_KEY --json(itsdata.gateway.namescontainsOPENROUTER_API_KEY;data.gateway.versionsthe same map). Neverechothe variable and never put the key on a command line. The change is live at once; no deploy. - You: verify (next section). The tab from step 5 can be closed once
the key answers there (Verify 2 on the
--from-envpath, otherwise 3). - You, openmail only: the provider is chosen when its Worker is
built, from the
AI_PROVIDERvariable of the deploying shell; the default is stillworkers-ai(the inbox's PR #6 flips it to OpenRouter; open). Pass it on every deploy, in the app's directory:AI_PROVIDER=openrouter pnpm everyapp deploy --json. OpenSEO and Open CRM need no redeploy.
Verify
pnpm everyapp secret list <app id> --jsonfor each app (openseo,crm,openmail), from anywhere: theOPENROUTER_API_KEYentry indata.app.setup.settingshas"set": trueand has leftdata.app.setup.unmet;data.app.setup.readyis true once nothing else of the app's is missing (OpenSEO: its DataForSEO key; Open CRM: its Context key; openmail: its email sender.WORKERS_AI_API_TOKENand OpenSEO's Context key are optional: insettingswithset: false, never inunmet). In the older fields,data.gateway.nameslists the key, itsdata.gateway.neededentry has"set": trueand it leftdata.gateway.missing(for an app not deployed yet, the rootpnpm everyapp secret list --jsonshowsdata.gateway.namesand that is the check). Thenpnpm everyapp status --json: each app'ssetup.readyis true anddata.fixeshas noAPP_NEEDS_SETUPfor it once everything else is set; until then the fix stays, naming only what is still missing.- Smoke check, only when the key went in with
--from-env(step 6's alternative: the variable already exists in the person's shell). After the hidden prompt nothing is exported, and after the management-key path nobody but the Gateway holds the key: don't ask them to export it for a check; go to 3, the app, where a $0 balance also shows (as a 402). In their terminal, never yours:curl -s -w '\n%{http_code}\n' https://openrouter.ai/api/v1/key -H "Authorization: Bearer $OPENROUTER_API_KEY". A{"data":{...}}body and 200 means the key works: readdata.label,data.limit(the step 5 limit, or null),data.limit_reset(null means a lifetime limit),data.limit_remaining,data.usage,data.expires_at(null means never) anddata.is_free_tier(false once credits were ever bought). The balance is not in this response andGET /api/v1/creditsneeds a management key: a $0 balance first shows as a 402 in 3. An{"error":...}body and 401: "User not found." is a wrong, disabled or mistyped key; "Missing Authentication header" means the variable is empty, so the export failed, not the key. Afterwards:unset OPENROUTER_API_KEY. - The app. OpenSEO: open
https://openseo.<app base domain>, a project, then its SAM page (the project's/samroute) and ask something; the answer streams. Prompt suggestions and website research use the same key. A browser sent tohttps://<gateway host>/?setup=openseoinstead means a required setting is still unset (Verify 1 says which): behind the Gateway's setup gate none of the three apps opens while this key is missing, so the refusals below are what a fork whose manifest lists the provider in an optional feature sees. OpenSEO's SAM page itself only checks for the Gateway binding, so there the turn fails: the Gateway answers 503ai_provider_not_configured, visible in the browser's network tab or the Gateway's logs (4). OpenSEO's own "OPENROUTER_API_KEY is not set" page shows only in local dev. Open CRM: openhttps://crm.<app base domain>; its research agent runs on the key. openmail (deployed withAI_PROVIDER=openrouter): openhttps://openmail.<app base domain>, a mailbox, and send the agent a message; it answers. Its refusal reads "AI isn't set up on this gateway yet, so I can't help with this inbox. An admin sets OPENROUTER_API_KEY withpnpm everyapp secret set OPENROUTER_API_KEY." (any other refusal: "The gateway refused the AI call: <message> (<code>)"). - The Gateway's logs, when the app says nothing useful: every proxied call
logs one JSON line
{"evt":"ai.proxy.usage","provider":"openrouter","model":...,"status":...,"usage":...}and a failed connection{"evt":"ai.proxy.upstream_failed",...}. Workers Logs is on for the Gateway Worker: in the Cloudflare dashboard, the setup rules' path: Workers & Pages →every-app-gateway→ Observability (Logs on older accounts) → Live, or the stored logs on the same page (kept 7 days on Workers Paid).cf workers taildoes not exist in cf 1.0.0-beta.14 (cf workers --help);cf o11y telemetry live-tail --helpandcf o11y telemetry query --helpare the CLI way, not covered here.statusis OpenRouter's HTTP status: 200 is a billed answer, 402 and 403 are the account's problem, not the Gateway's.
If it fails
| Error or symptom | Meaning | What to do |
|---|---|---|
SECRET_VALUE_REQUIRED |
secret set --json ran with nothing piped and no terminal |
one of the setup rules' three ways; its data.rerun is the --from-env OPENROUTER_API_KEY form |
SECRET_VALUE_REQUIRED from pnpm everyapp configure <app id> --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 app still needs |
the person's terminal (step 6), NAME=value lines on stdin, or the hidden read then its data.rerun, pnpm everyapp configure <app id> --from-env |
SECRET_EMPTY |
the --from-env variable is empty in your shell (exported in theirs, not before you started) |
the person runs the --from-env command in their terminal, or the hidden prompt (step 6) |
SECRET_SEVERAL_NAMES |
--from-env or a pipe with more than one name |
one name per call |
SECRET_NOT_DECLARED with data.didYouMean |
the name is misspelt (OPEN_ROUTER_API_KEY) |
rerun with the name in data.didYouMean and the same value source (--from-env VAR --json, or the same pipe); data.rerun carries only the names, so run as is it stops with SECRET_VALUE_REQUIRED |
provider_not_allowed (403) |
the app's everyapp.config.ts providers doesn't list openrouter |
add it and redeploy; nothing to set on the Gateway |
a browser opening the app is sent to https://<gateway host>/?setup=<app id>; any other request gets 503 with the body {"error":"app_needs_setup","app":"<app id>"} |
the Gateway's setup gate: a required setting of the app is still unset (this key, or another the manifest requires: openmail's email sender, OpenSEO's DataForSEO key, Open CRM's Context key) | data.app.setup.command, pnpm everyapp configure <app id> (step 6), then Verify 1 |
ai_provider_not_configured (503, x-everyapp-error) |
the Gateway holds no OPENROUTER_API_KEY (behind the setup gate an app whose manifest requires openrouter doesn't open in this state; one that lists it in an optional feature sees this) |
step 6, then Verify 1; check the name |
model_not_allowed (403) |
the app's manifest ai.models doesn't list the model it asked for (openmail's AGENT_MODEL) |
use a listed model, or add it to ai.models and redeploy; OpenSEO lists none, so any model passes |
ai_route_not_allowed (403) |
the app called a path that isn't inference | only the four routes under Settings; nothing to set |
ai_upstream_failed (502) |
the Gateway couldn't reach openrouter.ai | retry; read the ai.proxy.upstream_failed log line |
integration_not_configured |
a different capability's setting is missing: DataForSEO, Context or email sending, not OpenRouter | dataforseo.md, context.md, email.md |
| OpenRouter 401 "User not found." | the key is wrong, disabled, deleted, or pasted with a stray character | create a new key (Rerun, rotate, remove) |
| OpenRouter 401 "API key expired" | the key's Expiration (step 5) passed (wording from the management-key docs; unverified for an inference key) | a new key with No expiration (Rotate) |
| OpenRouter 401 "Missing Authentication header" / "No cookie auth credentials found" | the header was sent empty (the variable isn't exported in that terminal) or not at all; the key wasn't judged | redo the export in one terminal, then Verify 2; or step 6 |
| OpenRouter 402 "insufficient credits" | the balance is zero or negative | add credits at https://openrouter.ai/settings/credits |
OpenRouter 403 with limit_source: openrouter_key_limit |
the key's own credit limit (step 5) is spent; resets_at: null means a lifetime limit |
raise it on the key's page (the only fix for a lifetime limit), or wait for resets_at |
OpenRouter 403 guardrail or moderation (permission_denied) |
the prompt was flagged, or a workspace guardrail blocked the request; not a setup problem | nothing to set |
| OpenRouter 404 "0 endpoints out of N requested are available matching your guardrail restrictions and data policy" (older: "No endpoints found matching your data policy") | the privacy settings (training, or a zero-data-retention toggle for the model's group) exclude every endpoint of that model | https://openrouter.ai/settings/privacy, or another model |
| OpenRouter 429 | rate limited: a :free model's cap, or the upstream provider |
the apps' models are paid, so retry; OpenRouter routes to another provider when it can |
| OpenRouter 502 / 503 | the model is down, or no provider meets the routing requirements (zdr, provider order) |
retry later; OpenSEO's MiniMax fallback restricts routing to zero-data-retention endpoints (Together first, fallbacks on), so a 503 there means no ZDR provider is up |
| OpenRouter 403 "This model is not available in your region" | OpenRouter refuses OpenAI, Anthropic and Google models from some countries (community reports, 2026; not in OpenRouter's docs) | OpenSEO switches SAM to minimax/minimax-m3 by itself; openmail's agent defaults to Claude Haiku, so redeploy it with AGENT_MODEL=moonshotai/kimi-k2.6 AI_PROVIDER=openrouter pnpm everyapp deploy --json (or meta-llama/llama-4-scout, also in its ai.models) |
| openmail: "AI isn't set up on this gateway (OPENROUTER_API_KEY) or in .dev.vars" | no Gateway binding and no local key: everyapp dev, whose ready block's Dev vars line names the keys .dev.vars is missing (the inbox's names both providers' settings; on OpenRouter only OPENROUTER_API_KEY is needed) |
the person adds OPENROUTER_API_KEY=... to the app's .dev.vars (gitignored) in their own editor, with their own key, billed to them; never ask for the value and never write that file yourself |
| OpenSEO SAM page: "OPENROUTER_API_KEY is not set for this deployment yet" | the same, in local dev (its Dev vars line names the key too); behind the Gateway the page never says this |
.dev.vars as above, by the person |
Rerun, rotate, remove
- Rerun. Every step is safe to repeat:
configureandsecret setreplace the value, the keys page shows what exists. Start from Verify and continue at the first step that isn't done. - Rotate. Create a new key (step 5, No expiration), set it (step 6), run Verify, then the person deletes the old key at https://openrouter.ai/settings/keys. Apps pick up the new value on their next call; no deploy. A key with an Expiration date needs this before the date.
- Remove.
pnpm everyapp secret unset OPENROUTER_API_KEY --json(it asks at a terminal; with--jsonadd--yesonly when the person said so). Then the person deletes the key on OpenRouter: a key the Gateway no longer holds is still a key. Apps whose manifest requiresopenrouterthen need setup again: the Gateway closes them until the key is back (OpenSEO, Open CRM and openmail alike, and the inbox'sAI_PROVIDER=workers-aibuild changes nothing: the manifest, not the build, says what is required;statussays so, with this guide's path). An app that lists the provider in an optional feature keeps opening and getsai_provider_not_configuredon each call instead.
Sources
- https://openrouter.ai/docs/api_reference/authentication (keys at
/settings/keys, credit limit and reset on a key, onboarding keys' $100 limit and 180 days, Bearer header) - https://openrouter.ai/docs/api/api-reference/api-keys/get-current-api-key.md (
GET /api/v1/keyand its fields; no balance) - https://openrouter.ai/docs/api/api-reference/api-keys/create-a-new-api-key.md (
POST /api/v1/keys:limit,limit_reset,expires_at; top-levelkeyshown once; management key required) - https://openrouter.ai/docs/api/api-reference/credits/get-remaining-credits.md (
GET /api/v1/creditsneeds a management key; 403 otherwise) - https://openrouter.ai/docs/guides/overview/auth/management-api-keys (
/settings/management-keys, Create New Key, no completion calls, 401 "API key expired") - https://openrouter.ai/docs/api_reference/limits (free-model caps, paid models uncapped, negative balance,
is_free_tier) - https://openrouter.ai/docs/api_reference/errors-and-debugging (401, 402, 403 guardrail and
permission_denied, 429, 502, 503; key-limit 403 withlimit_source,scope,resets_at) - https://openrouter.ai/blog/announcements/simplifying-our-platform-fee/ and https://openrouter.ai/terms §4.1-4.2 (5.5% with a $0.80 minimum, 5% in crypto; $5 to $25,000 a purchase; 365-day expiry); https://openrouter.ai/docs/faq (payment methods, no inference markup)
- https://openrouter.ai/docs/guides/privacy/provider-logging (
/settings/privacy, paid and free settings), https://openrouter.ai/docs/guides/features/zdr (ZDR toggle per model group), https://openrouter.ai/docs/guides/routing/provider-selection (404 when no provider matches;order,zdr,allow_fallbacks) - https://github.com/NousResearch/hermes-agent/issues/133850 (2026-10-06; the data-policy 404 body, old and new wording)
- https://clerk.openrouter.ai/v1/environment (sign-in options; fetched 2026-10-23)
- https://linux.do/t/topic/1846933 and https://github.com/anomalyco/opencode/issues/19117 (2026; 403 "not available in your region" for OpenAI, Anthropic and Google models through OpenRouter)
- https://developers.cloudflare.com/workers/observability/logs/workers-logs/ (Workers & Pages, the Worker, Observability; 7 days on Paid);
cf o11y telemetry --help(live-tail, query) - https://openrouter.ai/docs/guides/routing/model-variants/free.md (
:freevariants) - https://openrouter.ai/api/v1/models (public; the six model ids and prices above, fetched 2026-10-23)
- Console pages checked on 2026-10-23: https://openrouter.ai/settings/keys (via
/workspace-redirect/keys), https://openrouter.ai/settings/credits and https://openrouter.ai/settings/privacy all go to sign-in when signed out; the older/docs/api-reference/...and/docs/features/...URLs 308-redirect to the pages above - In code:
apps/every-app-gateway/src/server/app-gateway-entrypoint.ts(provider_not_allowed),apps/every-app-gateway/src/server/capabilities/email.ts(integration_not_configured),apps/every-app-gateway/src/server/app-needs-setup.ts(the setup gate: the redirect to/?setup=<app id>and 503app_needs_setup),packages/sdk/src/internal/gatewaySettings.ts(each provider's key isrequired),packages/sdk/src/internal/appSettings.ts(AppSetup:unmet,settings,features,command; a provider is required unless an optional feature lists it;setup/openrouter.mdfrom the provider registry,packages/sdk/src/internal/aiProviders.ts),packages/cli/src/commands/secret/index.ts(secret list <app>from anywhere,data.app.setup, the keptdata.gateway.neededandmissing,APP_NOT_FOUND),packages/cli/src/commands/configure/index.ts(configure <app>: the prompts,NAME=valuelines on stdin,--from-env,SECRET_VALUE_REQUIRED),packages/cli/src/lib/app/settings.ts(theGuide:line),packages/cli/src/commands/status/problems.ts(APP_NEEDS_SETUP),packages/cli/src/commands/dev/ready.ts(theDev varsline); openmail'severyapp.config.ts(openrouterrequired,workers-aiin an optional feature),cloudflare.config.ts(AI_PROVIDERdefaults toworkers-aiuntil its PR #6) andworkers/everyapp/gateway.ts(AGENT_MODEL); OpenSEO'severyapp.config.ts(openrouterrequired),src/server/lib/openrouter.ts(ZDR routing, blocked countries) andsrc/serverFunctions/samAccess.ts(the SAM gate); Open CRM'severyapp.config.ts(openrouterrequired)
The default model
Step-by-step guide: give apps that say ai: { chat: true } a default model with one provider's key (OpenRouter first), pick it with everyapp ai default, check it.
OpenAI for the Gateway
Step-by-step guide: an OpenAI key (OPENAI_API_KEY) on the Gateway for apps that list the openai provider: the account, billing, the key, the check, rotation.